Merge "Restart IMM connection when running on automitive visible bkg user" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b4127c5..6f8a189 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@
         "android.app.flags-aconfig-java",
         "android.app.ondeviceintelligence-aconfig-java",
         "android.app.smartspace.flags-aconfig-java",
+        "android.app.supervision.flags-aconfig-java",
         "android.app.usage.flags-aconfig-java",
         "android.app.wearable.flags-aconfig-java",
         "android.appwidget.flags-aconfig-java",
@@ -99,6 +100,7 @@
         "framework-jobscheduler-job.flags-aconfig-java",
         "framework_graphics_flags_java_lib",
         "hwui_flags_java_lib",
+        "interaction_jank_monitor_flags_lib",
         "libcore_exported_aconfig_flags_lib",
         "libgui_flags_java_lib",
         "power_flags_lib",
@@ -436,10 +438,23 @@
     name: "android.companion.virtualdevice.flags-aconfig",
     package: "android.companion.virtualdevice.flags",
     container: "system",
+    exportable: true,
     srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
 }
 
 java_aconfig_library {
+    name: "android.companion.virtualdevice.flags-aconfig-java-export",
+    aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
+}
+
+java_aconfig_library {
     name: "android.companion.virtual.flags-aconfig-java",
     aconfig_declarations: "android.companion.virtual.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
@@ -1212,6 +1227,21 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Supervision
+aconfig_declarations {
+    name: "android.app.supervision.flags-aconfig",
+    exportable: true,
+    package: "android.app.supervision.flags",
+    container: "system",
+    srcs: ["core/java/android/app/supervision/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.supervision.flags-aconfig-java",
+    aconfig_declarations: "android.app.supervision.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // SurfaceFlinger
 java_aconfig_library {
     name: "surfaceflinger_flags_java_lib",
@@ -1549,3 +1579,17 @@
     aconfig_declarations: "dropbox_flags",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Zero Jank
+aconfig_declarations {
+    name: "interaction_jank_monitor_flags",
+    package: "com.android.internal.jank",
+    container: "system",
+    srcs: ["core/java/com/android/internal/jank/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "interaction_jank_monitor_flags_lib",
+    aconfig_declarations: "interaction_jank_monitor_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java
similarity index 60%
rename from apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
rename to apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java
index e1edb37..f4759b8 100644
--- a/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtologPerfTest.java
+++ b/apct-tests/perftests/protolog/src/com/android/internal/protolog/ProtoLogPerfTest.java
@@ -15,43 +15,54 @@
  */
 package com.android.internal.protolog;
 
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
+import android.app.Activity;
+import android.os.Bundle;
+import android.perftests.utils.Stats;
+
+import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collection;
 
 @RunWith(Parameterized.class)
-public class ProtologPerfTest {
-    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
-
-    @Parameters(name="logToProto_{0}_logToLogcat_{1}")
-    public static Collection<Object[]> params() {
-        return Arrays.asList(new Object[][] {
-                { true, true },
-                { true, false },
-                { false, true },
-                { false, false }
-        });
-    }
-
+public class ProtoLogPerfTest {
     private final boolean mLogToProto;
     private final boolean mLogToLogcat;
 
-    public ProtologPerfTest(boolean logToProto, boolean logToLogcat) {
-        mLogToProto = logToProto;
-        mLogToLogcat = logToLogcat;
+    /**
+     * Generates the parameters used for this test class
+     */
+    @Parameters(name = "LOG_TO_{0}")
+    public static Collection<Object[]> params() {
+        var params = new ArrayList<Object[]>();
+
+        for (LogTo logTo : LogTo.values()) {
+            params.add(new Object[] { logTo });
+        }
+
+        return params;
+    }
+
+    public ProtoLogPerfTest(LogTo logTo) {
+        mLogToProto = switch (logTo) {
+            case ALL, PROTO_ONLY -> true;
+            case LOGCAT_ONLY, NONE -> false;
+        };
+
+        mLogToLogcat = switch (logTo) {
+            case ALL, LOGCAT_ONLY -> true;
+            case PROTO_ONLY, NONE -> false;
+        };
     }
 
     @BeforeClass
@@ -66,11 +77,11 @@
     }
 
     @Test
-    public void logProcessedProtoLogMessageWithoutArgs() {
+    public void log_Processed_NoArgs() {
         final var protoLog = ProtoLog.getSingleInstance();
+        final var perfMonitor = new PerfMonitor();
 
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
+        while (perfMonitor.keepRunning()) {
             protoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                     0, (Object[]) null);
@@ -78,11 +89,11 @@
     }
 
     @Test
-    public void logProcessedProtoLogMessageWithArgs() {
+    public void log_Processed_WithArgs() {
         final var protoLog = ProtoLog.getSingleInstance();
+        final var perfMonitor = new PerfMonitor();
 
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
+        while (perfMonitor.keepRunning()) {
             protoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                     0b1110101001010100,
@@ -91,18 +102,58 @@
     }
 
     @Test
-    public void logNonProcessedProtoLogMessageWithNoArgs() {
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
+    public void log_Unprocessed_NoArgs() {
+        final var perfMonitor = new PerfMonitor();
+
+        while (perfMonitor.keepRunning()) {
             ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
         }
     }
 
     @Test
-    public void logNonProcessedProtoLogMessageWithArgs() {
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test messag %s, %d, %b", "arg1", 2, true);
+    public void log_Unprocessed_WithArgs() {
+        final var perfMonitor = new PerfMonitor();
+
+        while (perfMonitor.keepRunning()) {
+            ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message %s, %d, %b", "arg1", 2, true);
+        }
+    }
+
+    private class PerfMonitor {
+        private int mIteration = 0;
+
+        private static final int WARM_UP_ITERATIONS = 10;
+        private static final int ITERATIONS = 1000;
+
+        private final ArrayList<Long> mResults = new ArrayList<>();
+
+        private long mStartTimeNs;
+
+        public boolean keepRunning() {
+            final long currentTime = System.nanoTime();
+
+            mIteration++;
+
+            if (mIteration > ITERATIONS) {
+                reportResults();
+                return false;
+            }
+
+            if (mIteration > WARM_UP_ITERATIONS) {
+                mResults.add(currentTime - mStartTimeNs);
+            }
+
+            mStartTimeNs = System.nanoTime();
+            return true;
+        }
+
+        public void reportResults() {
+            final var stats = new Stats(mResults);
+
+            Bundle status = new Bundle();
+            status.putLong("protologging_time_mean_ns", (long) stats.getMean());
+            status.putLong("protologging_time_median_ns", (long) stats.getMedian());
+            InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
         }
     }
 
@@ -168,4 +219,11 @@
             return ordinal();
         }
     }
+
+    private enum LogTo {
+        PROTO_ONLY,
+        LOGCAT_ONLY,
+        ALL,
+        NONE,
+    }
 }
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 613b784..e5389b4 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -65,3 +65,13 @@
    description: "Remove started user if user will be stopped due to user switch"
    bug: "321598070"
 }
+
+flag {
+   name: "use_correct_process_state_for_logging"
+   namespace: "backstage_power"
+   description: "Use correct process state for statsd logging"
+   bug: "361308212"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d65a66c..be8e304 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -473,6 +473,14 @@
             mInitialDownloadedBytesFromCalling = TrafficStats.getUidRxBytes(job.getUid());
             mInitialUploadedBytesFromCalling = TrafficStats.getUidTxBytes(job.getUid());
 
+            int procState = mService.getUidProcState(job.getUid());
+            if (Flags.useCorrectProcessStateForLogging()
+                    && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                // Try to get the latest proc state from AMS, there might be some delay
+                // for the proc states worse than TRANSIENT_BACKGROUND.
+                procState = mActivityManagerInternal.getUidProcessState(job.getUid());
+            }
+
             FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                     job.isProxyJob() ? new int[]{sourceUid, job.getUid()} : new int[]{sourceUid},
                     // Given that the source tag is set by the calling app, it should be connected
@@ -517,7 +525,7 @@
                     job.getEstimatedNetworkDownloadBytes(),
                     job.getEstimatedNetworkUploadBytes(),
                     job.getWorkCount(),
-                    ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
+                    ActivityManager.processStateAmToProto(procState),
                     job.getNamespaceHash(),
                     /* system_measured_source_download_bytes */ 0,
                     /* system_measured_source_upload_bytes */ 0,
@@ -1528,6 +1536,13 @@
         mJobPackageTracker.noteInactive(completedJob,
                 loggingInternalStopReason, loggingDebugReason);
         final int sourceUid = completedJob.getSourceUid();
+        int procState = mService.getUidProcState(completedJob.getUid());
+        if (Flags.useCorrectProcessStateForLogging()
+                && procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+            // Try to get the latest proc state from AMS, there might be some delay
+            // for the proc states worse than TRANSIENT_BACKGROUND.
+            procState = mActivityManagerInternal.getUidProcessState(completedJob.getUid());
+        }
         FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.isProxyJob()
                         ? new int[]{sourceUid, completedJob.getUid()} : new int[]{sourceUid},
@@ -1573,7 +1588,7 @@
                 completedJob.getEstimatedNetworkUploadBytes(),
                 completedJob.getWorkCount(),
                 ActivityManager
-                        .processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
+                        .processStateAmToProto(procState),
                 completedJob.getNamespaceHash(),
                 TrafficStats.getUidRxBytes(completedJob.getSourceUid())
                         - mInitialDownloadedBytesFromSource,
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index d92351d..c9d3407 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,10 +1989,8 @@
                 mAdminProtectedPackages.put(userId, packageNames);
             }
         }
-        if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
-            if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
-                postCheckIdleStates(userId);
-            }
+        if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
+            postCheckIdleStates(userId);
         }
     }
 
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index d991da5..b3a674f 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -890,7 +890,7 @@
     cmd: "rm -f $(genDir)/framework.aidl.merged && " +
         "for i in $(in); do " +
         "  rm -f $(genDir)/framework.aidl.tmp && " +
-        "  $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp && " +
+        "  $(location sdkparcelables) $$i $(genDir)/framework.aidl.tmp --guarantee_stable && " +
         "  cat $(genDir)/framework.aidl.tmp >> $(genDir)/framework.aidl.merged; " +
         "done && " +
         "sort -u $(genDir)/framework.aidl.merged > $(out)",
diff --git a/core/api/current.txt b/core/api/current.txt
index c7df662..ed8ab06 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -141,7 +141,7 @@
     field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
     field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
     field public static final String MANAGE_DEVICE_POLICY_APP_USER_DATA = "android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA";
-    field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
+    field public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
     field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT";
     field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL";
     field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE";
@@ -169,7 +169,7 @@
     field public static final String MANAGE_DEVICE_POLICY_LOCK = "android.permission.MANAGE_DEVICE_POLICY_LOCK";
     field public static final String MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS = "android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS";
     field public static final String MANAGE_DEVICE_POLICY_LOCK_TASK = "android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK";
-    field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
+    field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS";
     field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA";
     field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE";
     field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE";
@@ -7875,7 +7875,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
     field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
-    field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
+    field public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
     field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
     field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
     field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
@@ -7968,7 +7968,7 @@
     field public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
     field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
     field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
-    field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String SECURITY_LOGGING_POLICY = "securityLogging";
+    field public static final String SECURITY_LOGGING_POLICY = "securityLogging";
     field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
     field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
     field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
@@ -8081,7 +8081,7 @@
     method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
     method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
     method public int getStorageEncryptionStatus();
-    method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
     method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -34116,7 +34116,7 @@
     field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
     field public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
-    field @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled") public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
+    field public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
     field public static final String DISALLOW_AUTOFILL = "no_autofill";
     field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
@@ -34166,7 +34166,7 @@
     field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
     field public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
     field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
-    field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
+    field public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
     field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
@@ -43968,8 +43968,11 @@
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_esos_inactivity_timeout_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
-    field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_screen_off_inactivity_timeout_duration_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_p2p_sms_inactivity_timeout_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_screen_off_inactivity_timeout_sec_int";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -54922,6 +54925,8 @@
     method @Deprecated public void addAction(int);
     method public void addChild(android.view.View);
     method public void addChild(android.view.View, int);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
     method public boolean canOpenPopup();
     method public int describeContents();
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54950,6 +54955,7 @@
     method public int getInputType();
     method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
     method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
     method public int getLiveRegion();
     method public int getMaxTextLength();
     method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -55010,6 +55016,8 @@
     method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
     method public boolean removeChild(android.view.View);
     method public boolean removeChild(android.view.View, int);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
+    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
     method public void setAccessibilityDataSensitive(boolean);
     method public void setAccessibilityFocused(boolean);
     method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e40cbc1..f26522b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -201,7 +201,7 @@
     field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
-    field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
+    field public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
     field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
@@ -1296,7 +1296,7 @@
   }
 
   public final class DevicePolicyIdentifiers {
-    field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging";
+    field public static final String AUDIT_LOGGING_POLICY = "auditLogging";
   }
 
   public class DevicePolicyKeyguardService extends android.app.Service {
@@ -1308,7 +1308,7 @@
 
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1328,7 +1328,7 @@
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
     method public boolean isDeviceManaged();
     method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1344,8 +1344,8 @@
     method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
-    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -1422,7 +1422,7 @@
     field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
     field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
     field public static final int STATUS_HAS_PAIRED = 8; // 0x8
-    field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
+    field public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
     field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
     field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
     field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
@@ -3694,9 +3694,11 @@
     method public int getMinDelay();
     method @NonNull public String getName();
     method public float getPower();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public int getReportingMode();
     method public float getResolution();
     method public int getType();
     method @Nullable public String getVendor();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
   }
@@ -3710,8 +3712,10 @@
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaximumRange(float);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMinDelay(int);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setPower(float);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setReportingMode(int);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setResolution(float);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setWakeUpSensor(boolean);
   }
 
   public interface VirtualSensorDirectChannelCallback {
@@ -8083,6 +8087,7 @@
   public class Tuner implements java.lang.AutoCloseable {
     ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
     method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
+    method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @RequiresPermission(allOf={"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"}) public int applyFrontendByType(int);
     method public int cancelScanning();
     method public int cancelTuning();
     method public void clearOnTuneEventListener();
@@ -11155,6 +11160,7 @@
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
     method @Deprecated @RequiresPermission(android.Manifest.permission.RECOVERY) public static void rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
     method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static int rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
+    method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @RequiresPermission(android.Manifest.permission.RECOVERY) public static void rebootPromptAndWipeUserData(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
     method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
     method @Deprecated public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
@@ -14219,7 +14225,7 @@
     field public static final int CAPABILITY_EMERGENCY_PREFERRED = 8192; // 0x2000
     field public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 512; // 0x200
     field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
-    field public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
+    field @Deprecated @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_PLAY_CALL_RECORDING_TONE = "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
     field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String EXTRA_SKIP_CALL_FILTERING = "android.telecom.extra.SKIP_CALL_FILTERING";
     field public static final String EXTRA_SORT_ORDER = "android.telecom.extra.SORT_ORDER";
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8dd4adc..009d082 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1542,6 +1542,10 @@
     method @Deprecated public final void setPreviewSurface(android.view.Surface) throws java.io.IOException;
   }
 
+  public final class Sensor {
+    method public int getHandle();
+  }
+
   public final class SensorPrivacyManager {
     method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
@@ -1992,6 +1996,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isVolumeControlUsingVolumeGroups();
+    method public void permissionUpdateBarrier();
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setCsd(float);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setNotifAliasRingForTest(boolean);
@@ -3133,6 +3138,7 @@
 
   public abstract class DreamOverlayService extends android.app.Service {
     ctor public DreamOverlayService();
+    method @FlaggedApi("android.service.dreams.publish_preview_state_to_overlay") public final boolean isDreamInPreviewMode();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onEndDream();
     method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
@@ -3665,7 +3671,11 @@
     method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
     method public boolean hasAccess(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
+    field public static final int FLAG_ALWAYS_UNLOCKED = 512; // 0x200
+    field public static final int FLAG_OWN_FOCUS = 2048; // 0x800
+    field public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1024; // 0x400
     field public static final int FLAG_TRUSTED = 128; // 0x80
+    field public static final int REMOVE_MODE_DESTROY_CONTENT = 1; // 0x1
     field public static final int TYPE_EXTERNAL = 2; // 0x2
     field public static final int TYPE_INTERNAL = 1; // 0x1
     field public static final int TYPE_OVERLAY = 4; // 0x4
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d455853..4350545 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4583,7 +4583,7 @@
     public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
             @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
             @NonNull SurfaceControl startingWindowLeash) {
-        final DecorView decorView = (DecorView) r.window.peekDecorView();
+        final DecorView decorView = r.window != null ? (DecorView) r.window.peekDecorView() : null;
         if (parcelable != null && decorView != null) {
             createSplashScreen(r, decorView, parcelable, startingWindowLeash);
         } else {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 14195c4..63e03914 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1326,6 +1326,7 @@
         private boolean mClock;
         private boolean mNotificationIcons;
         private boolean mRotationSuggestion;
+        private boolean mQuickSettings;
 
         /** @hide */
         public DisableInfo(int flags1, int flags2) {
@@ -1338,6 +1339,7 @@
             mClock = (flags1 & DISABLE_CLOCK) != 0;
             mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
             mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
+            mQuickSettings = (flags2 & DISABLE2_QUICK_SETTINGS) != 0;
         }
 
         /** @hide */
@@ -1471,6 +1473,20 @@
         }
 
         /**
+         * @hide
+         */
+        public void setQuickSettingsDisabled(boolean disabled) {
+            mQuickSettings = disabled;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean isQuickSettingsDisabled() {
+            return mQuickSettings;
+        }
+
+        /**
          * @return {@code true} if no components are disabled (default state)
          * @hide
          */
@@ -1478,7 +1494,7 @@
         public boolean areAllComponentsEnabled() {
             return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
                     && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
-                    && !mRotationSuggestion;
+                    && !mRotationSuggestion && !mQuickSettings;
         }
 
         /** @hide */
@@ -1492,6 +1508,7 @@
             mClock = false;
             mNotificationIcons = false;
             mRotationSuggestion = false;
+            mQuickSettings = false;
         }
 
         /**
@@ -1502,7 +1519,7 @@
         public boolean areAllComponentsDisabled() {
             return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
                     && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
-                    && mRotationSuggestion;
+                    && mRotationSuggestion && mQuickSettings;
         }
 
         /** @hide */
@@ -1516,6 +1533,7 @@
             mClock = true;
             mNotificationIcons = true;
             mRotationSuggestion = true;
+            mQuickSettings = true;
         }
 
         @NonNull
@@ -1533,6 +1551,7 @@
             sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
             sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
             sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
+            sb.append(" mQuickSettings=").append(mQuickSettings ? "disabled" : "enabled");
 
             return sb.toString();
 
@@ -1557,6 +1576,7 @@
             if (mClock) disable1 |= DISABLE_CLOCK;
             if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
             if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
+            if (mQuickSettings) disable2 |= DISABLE2_QUICK_SETTINGS;
 
             return new Pair<Integer, Integer>(disable1, disable2);
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index cb38cf2..8b3ee24 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -48,6 +48,8 @@
 import android.app.search.SearchUiManager;
 import android.app.slice.SliceManager;
 import android.app.smartspace.SmartspaceManager;
+import android.app.supervision.ISupervisionManager;
+import android.app.supervision.SupervisionManager;
 import android.app.time.TimeManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timedetector.TimeDetectorImpl;
@@ -1703,6 +1705,21 @@
                         return new E2eeContactKeysManager(ctx);
                     }});
 
+        registerService(Context.SUPERVISION_SERVICE, SupervisionManager.class,
+                new CachedServiceFetcher<>() {
+                    @Override
+                    public SupervisionManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        if (!android.app.supervision.flags.Flags.supervisionApi()) {
+                            throw new ServiceNotFoundException(
+                                    "SupervisionManager is not supported");
+                        }
+                        IBinder iBinder = ServiceManager.getServiceOrThrow(
+                                Context.SUPERVISION_SERVICE);
+                        ISupervisionManager service = ISupervisionManager.Stub.asInterface(iBinder);
+                        return new SupervisionManager(ctx, service);
+                    }
+                });
         // DO NOT do a flag check like this unless the flag is read-only.
         // (because this code is executed during preload in zygote.)
         // If the flag is mutable, the check should be inside CachedServiceFetcher.
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 46c9e78..4f2efa4 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,9 +16,6 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.admin.flags.Flags;
@@ -195,7 +192,6 @@
      * DPCs should set the value of attribute "headless-device-owner-mode" inside the
      * "headless-system-user" tag as "single_user".
      */
-    @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index eeaf0b3..156512a 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -17,7 +17,6 @@
 package android.app.admin;
 
 import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
@@ -50,7 +49,6 @@
     /**
      * String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}.
      */
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     public static final String SECURITY_LOGGING_POLICY = "securityLogging";
 
     /**
@@ -58,7 +56,6 @@
      *
      * @hide
      */
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @SystemApi
     public static final String AUDIT_LOGGING_POLICY = "auditLogging";
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ba1dc56..d31d8f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -56,11 +56,8 @@
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
-import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
 import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -2989,7 +2986,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
 
     /**
@@ -14335,7 +14331,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEnabled(boolean enabled) {
         throwIfParentInstance("setAuditLogEnabled");
@@ -14352,7 +14347,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public boolean isAuditLogEnabled() {
         throwIfParentInstance("isAuditLogEnabled");
@@ -14374,7 +14368,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEventCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -14401,7 +14394,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void clearAuditLogEventCallback() {
         throwIfParentInstance("clearAuditLogEventCallback");
@@ -17749,7 +17741,6 @@
      * @throws SecurityException if the caller is not authorized to call this method.
      * @return ids of all managed subscriptions currently downloaded by an admin on the device.
      */
-    @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
     @NonNull
     public Set<Integer> getSubscriptionIds() {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 9148e3c..29a5048 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -105,6 +105,7 @@
   bug: "289520697"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "security_log_v2_enabled"
   is_exported: true
@@ -124,13 +125,6 @@
 }
 
 flag {
-  name: "dumpsys_policy_engine_migration_enabled"
-  namespace: "enterprise"
-  description: "Update DumpSys to include information about migrated APIs in DPE"
-  bug: "304999634"
-}
-
-flag {
     name: "allow_querying_profile_type"
     is_exported: true
     namespace: "enterprise"
@@ -145,6 +139,7 @@
   bug: "293441361"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "assist_content_user_restriction_enabled"
   is_exported: true
@@ -171,6 +166,7 @@
   bug: "304999634"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "esim_management_enabled"
   is_exported: true
@@ -179,6 +175,7 @@
   bug: "295301164"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "headless_device_owner_single_user_enabled"
   is_exported: true
@@ -206,26 +203,6 @@
 }
 
 flag {
-  name: "power_exemption_bg_usage_fix"
-  namespace: "enterprise"
-  description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
-  bug: "333379020"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
-  name: "disallow_user_control_bg_usage_fix"
-  namespace: "enterprise"
-  description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
-  bug: "326031059"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "disallow_user_control_stopped_state_fix"
   namespace: "enterprise"
   description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
new file mode 100644
index 0000000..3169f0e
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GlobalSearchSession;
+import android.app.appsearch.JoinSpec;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
+import android.os.OutcomeReceiver;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class containing utilities for {@link AppFunctionManager}.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionManagerHelper {
+
+    /**
+     * Returns (through a callback) a boolean indicating whether the app function is enabled.
+     * <p>
+     * This method can only check app functions that are owned by the caller owned by packages
+     * visible to the caller.
+     * <p>
+     * If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+     * <ul>
+     *     <li>{@link IllegalArgumentException}, if the function is not found</li>
+     *     <li>{@link SecurityException}, if the caller does not have permission to query the
+     *         target package
+     *     </li>
+     *  </ul>
+     *
+     * @param functionIdentifier the identifier of the app function to check (unique within the
+     *                           target package) and in most cases, these are automatically
+     *                           generated by the AppFunctions SDK
+     * @param targetPackage      the package name of the app function's owner
+     * @param appSearchExecutor  the executor to run the metadata search mechanism through AppSearch
+     * @param callbackExecutor   the executor to run the callback
+     * @param callback           the callback to receive the function enabled check result
+     * @hide
+     */
+    public static void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull String targetPackage,
+            @NonNull AppSearchManager appSearchManager,
+            @NonNull Executor appSearchExecutor,
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback
+    ) {
+        Objects.requireNonNull(functionIdentifier);
+        Objects.requireNonNull(targetPackage);
+        Objects.requireNonNull(appSearchManager);
+        Objects.requireNonNull(appSearchExecutor);
+        Objects.requireNonNull(callbackExecutor);
+        Objects.requireNonNull(callback);
+
+        appSearchManager.createGlobalSearchSession(appSearchExecutor,
+                (searchSessionResult) -> {
+                    if (!searchSessionResult.isSuccess()) {
+                        callbackExecutor.execute(() ->
+                                callback.onError(failedResultToException(searchSessionResult)));
+                        return;
+                    }
+                    try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
+                        SearchResults results = searchJoinedStaticWithRuntimeAppFunctions(
+                                searchSession, targetPackage, functionIdentifier);
+                        results.getNextPage(appSearchExecutor,
+                                listAppSearchResult -> callbackExecutor.execute(() -> {
+                                    if (listAppSearchResult.isSuccess()) {
+                                        callback.onResult(getEnabledStateFromSearchResults(
+                                                Objects.requireNonNull(
+                                                        listAppSearchResult.getResultValue())));
+                                    } else {
+                                        callback.onError(
+                                                failedResultToException(listAppSearchResult));
+                                    }
+                                }));
+                    } catch (Exception e) {
+                        callbackExecutor.execute(() -> callback.onError(e));
+                    }
+                });
+    }
+
+    /**
+     * Searches joined app function static and runtime metadata using the function Id and the
+     * package.
+     *
+     * @hide
+     */
+    private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
+            @NonNull GlobalSearchSession session,
+            @NonNull String targetPackage,
+            @NonNull String functionIdentifier) {
+        SearchSpec runtimeSearchSpec = getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+                targetPackage);
+        JoinSpec joinSpec = new JoinSpec.Builder(
+                PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+                .setNestedSearch(
+                        functionIdentifier,
+                        runtimeSearchSpec).build();
+        SearchSpec joinedStaticWithRuntimeSearchSpec = new SearchSpec.Builder()
+                .setJoinSpec(joinSpec)
+                .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+                .addFilterSchemas(
+                        AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
+                                targetPackage))
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build();
+        return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
+    }
+
+    /**
+     * Finds whether the function is enabled or not from the search results returned by
+     * {@link #searchJoinedStaticWithRuntimeAppFunctions}.
+     *
+     * @throws IllegalArgumentException if the function is not found in the results
+     * @hide
+     */
+    private static boolean getEnabledStateFromSearchResults(
+            @NonNull List<SearchResult> joinedStaticRuntimeResults) {
+        if (joinedStaticRuntimeResults.isEmpty()) {
+            // Function not found.
+            throw new IllegalArgumentException("App function not found.");
+        } else {
+            List<SearchResult> runtimeMetadataResults =
+                    joinedStaticRuntimeResults.getFirst().getJoinedResults();
+            if (!runtimeMetadataResults.isEmpty()) {
+                Boolean result = (Boolean) runtimeMetadataResults
+                        .getFirst().getGenericDocument()
+                        .getProperty(PROPERTY_ENABLED);
+                if (result != null) {
+                    return result;
+                }
+            }
+            // Runtime metadata not found. Using the default value in the static metadata.
+            return joinedStaticRuntimeResults.getFirst().getGenericDocument()
+                    .getPropertyBoolean(STATIC_PROPERTY_ENABLED_BY_DEFAULT);
+        }
+    }
+
+    /**
+     * Returns search spec that queries app function metadata for a specific package name by its
+     * function identifier.
+     *
+     * @hide
+     */
+    public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+            @NonNull String targetPackage) {
+        return new SearchSpec.Builder()
+                .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+                .addFilterSchemas(
+                        AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(
+                                targetPackage))
+                .addFilterProperties(
+                        AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(
+                                targetPackage),
+                        List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build();
+    }
+
+    /**
+     * Converts a failed app search result codes into an exception.
+     *
+     * @hide
+     */
+    public static @NonNull Exception failedResultToException(
+            @NonNull AppSearchResult appSearchResult) {
+        return switch (appSearchResult.getResultCode()) {
+            case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+                    appSearchResult.getErrorMessage());
+            default -> new IllegalStateException(appSearchResult.getErrorMessage());
+        };
+    }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
new file mode 100644
index 0000000..fdd12b0
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Represents runtime function metadata of an app function.
+ *
+ * <p>This is a temporary solution for app function indexing, as later we would like to index the
+ * actual function signature entity class shape instead of just the schema info.
+ *
+ * @hide
+ */
+// TODO(b/357551503): Link to canonical docs rather than duplicating once they
+// are available.
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionRuntimeMetadata extends GenericDocument {
+    public static final String RUNTIME_SCHEMA_TYPE = "AppFunctionRuntimeMetadata";
+    public static final String APP_FUNCTION_INDEXER_PACKAGE = "android";
+    public static final String APP_FUNCTION_RUNTIME_METADATA_DB = "appfunctions-db";
+    public static final String APP_FUNCTION_RUNTIME_NAMESPACE = "app_functions_runtime";
+    public static final String PROPERTY_FUNCTION_ID = "functionId";
+    public static final String PROPERTY_PACKAGE_NAME = "packageName";
+    public static final String PROPERTY_ENABLED = "enabled";
+    public static final String PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID =
+            "appFunctionStaticMetadataQualifiedId";
+    private static final String TAG = "AppSearchAppFunction";
+    private static final String RUNTIME_SCHEMA_TYPE_SEPARATOR = "-";
+
+    public AppFunctionRuntimeMetadata(@NonNull GenericDocument genericDocument) {
+        super(genericDocument);
+    }
+
+    /**
+     * Returns a per-app runtime metadata schema name, to store all functions for that package.
+     */
+    public static String getRuntimeSchemaNameForPackage(@NonNull String pkg) {
+        return RUNTIME_SCHEMA_TYPE + RUNTIME_SCHEMA_TYPE_SEPARATOR + Objects.requireNonNull(pkg);
+    }
+
+    /**
+     * Returns the document id for an app function's runtime metadata.
+     */
+    public static String getDocumentIdForAppFunction(
+            @NonNull String pkg, @NonNull String functionId) {
+        return pkg + "/" + functionId;
+    }
+
+    /**
+     * Different packages have different visibility requirements. To allow for different visibility,
+     * we need to have per-package app function schemas.
+     * <p>This schema should be set visible to callers from the package owner itself and for callers
+     * with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
+     * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+     *
+     * @param packageName The package name to create a schema for.
+     */
+    @NonNull
+    public static AppSearchSchema createAppFunctionRuntimeSchema(@NonNull String packageName) {
+        return new AppSearchSchema.Builder(getRuntimeSchemaNameForPackage(packageName))
+                .addProperty(
+                        new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_FUNCTION_ID)
+                                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingType(
+                                        AppSearchSchema.StringPropertyConfig
+                                                .INDEXING_TYPE_EXACT_TERMS)
+                                .setTokenizerType(
+                                        AppSearchSchema.StringPropertyConfig
+                                                .TOKENIZER_TYPE_VERBATIM)
+                                .build())
+                .addProperty(
+                        new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_PACKAGE_NAME)
+                                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setIndexingType(
+                                        AppSearchSchema.StringPropertyConfig
+                                                .INDEXING_TYPE_EXACT_TERMS)
+                                .setTokenizerType(
+                                        AppSearchSchema.StringPropertyConfig
+                                                .TOKENIZER_TYPE_VERBATIM)
+                                .build())
+                .addProperty(
+                        new AppSearchSchema.BooleanPropertyConfig.Builder(PROPERTY_ENABLED)
+                                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                .build())
+                .addProperty(
+                        new AppSearchSchema.StringPropertyConfig.Builder(
+                                PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+                                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setJoinableValueType(
+                                        AppSearchSchema.StringPropertyConfig
+                                                .JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+                                .build())
+                .addParentType(RUNTIME_SCHEMA_TYPE)
+                .build();
+    }
+
+    /**
+     * Returns the function id. This might look like "com.example.message#send_message".
+     */
+    @NonNull
+    public String getFunctionId() {
+        return Objects.requireNonNull(getPropertyString(PROPERTY_FUNCTION_ID));
+    }
+
+    /**
+     * Returns the package name of the package that owns this function.
+     */
+    @NonNull
+    public String getPackageName() {
+        return Objects.requireNonNull(getPropertyString(PROPERTY_PACKAGE_NAME));
+    }
+
+    /**
+     * Returns if the function is set to be enabled or not. If not set, the {@link
+     * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT} value would be used.
+     */
+    @Nullable
+    public Boolean getEnabled() {
+        return (Boolean) getProperty(PROPERTY_ENABLED);
+    }
+
+    /**
+     * Returns the qualified id linking to the static metadata of the app function.
+     */
+    @Nullable
+    @VisibleForTesting
+    public String getAppFunctionStaticMetadataQualifiedId() {
+        return getPropertyString(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID);
+    }
+
+    public static final class Builder extends GenericDocument.Builder<Builder> {
+        /**
+         * Creates a Builder for a {@link AppFunctionRuntimeMetadata}.
+         *
+         * @param packageName               the name of the package that owns the function.
+         * @param functionId                the id of the function.
+         * @param staticMetadataQualifiedId the qualified static metadata id that this runtime
+         *                                  metadata refers to.
+         */
+        public Builder(
+                @NonNull String packageName,
+                @NonNull String functionId,
+                @NonNull String staticMetadataQualifiedId) {
+            super(
+                    APP_FUNCTION_RUNTIME_NAMESPACE,
+                    getDocumentIdForAppFunction(
+                            Objects.requireNonNull(packageName),
+                            Objects.requireNonNull(functionId)),
+                    getRuntimeSchemaNameForPackage(packageName));
+            setPropertyString(PROPERTY_PACKAGE_NAME, packageName);
+            setPropertyString(PROPERTY_FUNCTION_ID, functionId);
+
+            // Set qualified id automatically
+            setPropertyString(
+                    PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID,
+                    staticMetadataQualifiedId);
+        }
+
+
+        /**
+         * Sets an indicator specifying if the function is enabled or not. This would override the
+         * default enabled state in the static metadata ({@link
+         * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}).
+         */
+        @NonNull
+        public Builder setEnabled(boolean enabled) {
+            setPropertyBoolean(PROPERTY_ENABLED, enabled);
+            return this;
+        }
+
+        /**
+         * Creates the {@link AppFunctionRuntimeMetadata} GenericDocument.
+         */
+        @NonNull
+        public AppFunctionRuntimeMetadata build() {
+            return new AppFunctionRuntimeMetadata(super.build());
+        }
+    }
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
new file mode 100644
index 0000000..6d4172a
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.appsearch.util.DocumentIdUtil;
+
+import java.util.Objects;
+
+/**
+ * Contains constants and helper related to static metadata represented with {@code
+ * com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionStaticMetadata}.
+ * <p>
+ * The constants listed here **must not change** and be kept consistent with the canonical
+ * static metadata class.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class AppFunctionStaticMetadataHelper {
+    public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
+    public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
+
+    public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
+
+    // These are constants that has to be kept the same with {@code
+    // com.android.server.appsearch.appsindexer.appsearchtypes.AppSearchHelper}.
+    public static final String APP_FUNCTION_STATIC_METADATA_DB = "apps-db";
+    public static final String APP_FUNCTION_INDEXER_PACKAGE = "android";
+
+    /**
+     * Returns a per-app static metadata schema name, to store all functions for that package.
+     */
+    public static String getStaticSchemaNameForPackage(@NonNull String pkg) {
+        return STATIC_SCHEMA_TYPE + "-" + Objects.requireNonNull(pkg);
+    }
+
+    /** Returns the document id for an app function's static metadata. */
+    public static String getDocumentIdForAppFunction(
+            @NonNull String pkg, @NonNull String functionId) {
+        return pkg + "/" + functionId;
+    }
+
+    /**
+     * Returns the fully qualified Id used in AppSearch for the given package and function id
+     * app function static metadata.
+     */
+    public static String getStaticMetadataQualifiedId(String packageName, String functionId) {
+        return DocumentIdUtil.createQualifiedId(
+                APP_FUNCTION_INDEXER_PACKAGE,
+                APP_FUNCTION_STATIC_METADATA_DB,
+                APP_FUNCTION_STATIC_NAMESPACE,
+                getDocumentIdForAppFunction(packageName, functionId));
+    }
+}
diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
new file mode 100644
index 0000000..8d25cad
--- /dev/null
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+/**
+ * Internal IPC interface to the supervision service.
+ * {@hide}
+ */
+interface ISupervisionManager {
+    boolean isSupervisionEnabled();
+}
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
new file mode 100644
index 0000000..8611a92
--- /dev/null
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.supervision;
+
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.RemoteException;
+
+/**
+ * Service for handling parental supervision.
+ *
+ * @hide
+ */
+@SystemService(Context.SUPERVISION_SERVICE)
+public class SupervisionManager {
+    private final Context mContext;
+    private final ISupervisionManager mService;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public SupervisionManager(Context context, ISupervisionManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns whether the device is supervised.
+     *
+     * @hide
+     */
+    public boolean isSupervisionEnabled() {
+        try {
+            return mService.isSupervisionEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
new file mode 100644
index 0000000..bcb5b36
--- /dev/null
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.app.supervision.flags"
+container: "system"
+
+flag {
+  name: "supervision_api"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag to enable the SupervisionService"
+  bug: "340351729"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index ee9114f..93d62cf 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -10,13 +10,6 @@
 }
 
 flag {
-    name: "companion_transport_apis"
-    namespace: "companion"
-    description: "Grants access to the companion transport apis."
-    bug: "288297505"
-}
-
-flag {
     name: "association_tag"
     is_exported: true
     namespace: "companion"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c3c3f0e..22a9ccf 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -50,6 +50,7 @@
      name: "activity_control_api"
      description: "Enable APIs for fine grained activity policy, fallback and callbacks"
      bug: "333443509"
+     is_exported: true
 }
 
 flag {
@@ -103,3 +104,10 @@
   description: "Expose multiple surface for the virtual camera owner for different stream resolution"
   bug: "341083465"
 }
+
+flag {
+    namespace: "virtual_devices"
+    name: "device_aware_display_power"
+    description: "Device awareness in power and display APIs"
+    bug: "285020111"
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 21ad914..68bc9bc 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -17,17 +17,27 @@
 package android.companion.virtual.sensor;
 
 
+import static android.hardware.Sensor.REPORTING_MODE_CONTINUOUS;
+import static android.hardware.Sensor.REPORTING_MODE_ONE_SHOT;
+import static android.hardware.Sensor.REPORTING_MODE_ON_CHANGE;
+import static android.hardware.Sensor.REPORTING_MODE_SPECIAL_TRIGGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.companion.virtualdevice.flags.Flags;
 import android.hardware.Sensor;
 import android.hardware.SensorDirectChannel;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 
@@ -42,6 +52,13 @@
 public final class VirtualSensorConfig implements Parcelable {
     private static final String TAG = "VirtualSensorConfig";
 
+    // Defined in sensors.h
+    private static final int FLAG_WAKE_UP_SENSOR = 1;
+
+    // Mask for the reporting mode, bit 2, 3, 4.
+    private static final int REPORTING_MODE_MASK = 0xE;
+    private static final int REPORTING_MODE_SHIFT = 1;
+
     // Mask for direct mode highest rate level, bit 7, 8, 9.
     private static final int DIRECT_REPORT_MASK = 0x380;
     private static final int DIRECT_REPORT_SHIFT = 7;
@@ -62,6 +79,17 @@
 
     private final int mFlags;
 
+    /** @hide */
+    @IntDef(prefix = "REPORTING_MODE_", value = {
+            REPORTING_MODE_CONTINUOUS,
+            REPORTING_MODE_ON_CHANGE,
+            REPORTING_MODE_ONE_SHOT,
+            REPORTING_MODE_SPECIAL_TRIGGER
+    })
+
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ReportingMode {}
+
     private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
             float maximumRange, float resolution, float power, int minDelay, int maxDelay,
             int flags) {
@@ -193,8 +221,7 @@
     @SensorDirectChannel.RateLevel
     public int getHighestDirectReportRateLevel() {
         int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
-        return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
-                ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+        return Math.min(rateLevel, SensorDirectChannel.RATE_VERY_FAST);
     }
 
     /**
@@ -215,6 +242,28 @@
     }
 
     /**
+     * Returns whether the sensor is a wake-up sensor.
+     *
+     * @see Builder#setWakeUpSensor(boolean)
+     * @see Sensor#isWakeUpSensor()
+     */
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public boolean isWakeUpSensor() {
+        return (mFlags & FLAG_WAKE_UP_SENSOR) > 0;
+    }
+
+    /**
+     * Returns the reporting mode of this sensor.
+     *
+     * @see Builder#setReportingMode(int)
+     * @see Sensor#getReportingMode()
+     */
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @ReportingMode int getReportingMode() {
+        return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT);
+    }
+
+    /**
      * Returns the sensor flags.
      *
      * @hide
@@ -383,6 +432,45 @@
             }
             return this;
         }
+
+        /**
+         * Sets whether this sensor is a wake up sensor.
+         *
+         * @see Sensor#isWakeUpSensor()
+         */
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public VirtualSensorConfig.Builder setWakeUpSensor(boolean wakeUpSensor) {
+            if (wakeUpSensor) {
+                mFlags |= FLAG_WAKE_UP_SENSOR;
+            } else {
+                mFlags &= ~FLAG_WAKE_UP_SENSOR;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the reporting mode of this sensor.
+         *
+         * @throws IllegalArgumentException if the reporting mode is not one of
+         *   {@link Sensor#REPORTING_MODE_CONTINUOUS}, {@link Sensor#REPORTING_MODE_ON_CHANGE},
+         *   {@link Sensor#REPORTING_MODE_ONE_SHOT}, or
+         *   {@link Sensor#REPORTING_MODE_SPECIAL_TRIGGER}.
+         *
+         * @see Sensor#getReportingMode()
+         */
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public VirtualSensorConfig.Builder setReportingMode(@ReportingMode int reportingMode) {
+            if (reportingMode != REPORTING_MODE_CONTINUOUS
+                    && reportingMode != REPORTING_MODE_ON_CHANGE
+                    && reportingMode != REPORTING_MODE_ONE_SHOT
+                    && reportingMode != REPORTING_MODE_SPECIAL_TRIGGER) {
+                throw new IllegalArgumentException("Invalid reporting mode: " + reportingMode);
+            }
+            mFlags |= reportingMode << REPORTING_MODE_SHIFT;
+            return this;
+        }
     }
 
     @NonNull
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 37f419d..ffed536 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -629,11 +629,10 @@
      * A builder for {@link AttributionSource}
      */
     public static final class Builder {
+        private boolean mHasBeenUsed;
         private @NonNull final AttributionSourceState mAttributionSourceState =
                 new AttributionSourceState();
 
-        private long mBuilderFieldsSet = 0L;
-
         /**
          * Creates a new Builder.
          *
@@ -642,8 +641,17 @@
          */
         public Builder(int uid) {
             mAttributionSourceState.uid = uid;
+            mAttributionSourceState.pid = Process.INVALID_PID;
+            mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
+            mAttributionSourceState.token = sDefaultToken;
         }
 
+        /**
+         * Creates a builder that is ready to build a new {@link AttributionSource} where
+         * all fields (primitive, immutable data, pointers) are copied from the given
+         * {@link AttributionSource}. Builder methods can still be used to mutate fields further.
+         * @param current The source to copy fields from.
+         */
         public Builder(@NonNull AttributionSource current) {
             if (current == null) {
                 throw new IllegalArgumentException("current AttributionSource can not be null");
@@ -652,9 +660,11 @@
             mAttributionSourceState.pid = current.getPid();
             mAttributionSourceState.packageName = current.getPackageName();
             mAttributionSourceState.attributionTag = current.getAttributionTag();
-            mAttributionSourceState.token = current.getToken();
             mAttributionSourceState.renouncedPermissions =
                 current.mAttributionSourceState.renouncedPermissions;
+            mAttributionSourceState.deviceId = current.getDeviceId();
+            mAttributionSourceState.next = current.mAttributionSourceState.next;
+            mAttributionSourceState.token = current.getToken();
         }
 
         /**
@@ -666,7 +676,6 @@
          */
         public @NonNull Builder setPid(int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
             mAttributionSourceState.pid = value;
             return this;
         }
@@ -676,7 +685,6 @@
          */
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
             mAttributionSourceState.packageName = value;
             return this;
         }
@@ -686,7 +694,6 @@
          */
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
             mAttributionSourceState.attributionTag = value;
             return this;
         }
@@ -717,11 +724,11 @@
          */
         @SystemApi
         @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
-        public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
+        public @NonNull Builder setRenouncedPermissions(
+                @Nullable Set<String> renouncedPermissions) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
-            mAttributionSourceState.renouncedPermissions = (value != null)
-                    ? value.toArray(new String[0]) : null;
+            mAttributionSourceState.renouncedPermissions = (renouncedPermissions != null)
+                    ? renouncedPermissions.toArray(new String[0]) : null;
             return this;
         }
 
@@ -734,7 +741,6 @@
         @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
         public @NonNull Builder setDeviceId(int deviceId) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x12;
             mAttributionSourceState.deviceId = deviceId;
             return this;
         }
@@ -744,7 +750,6 @@
          */
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
                     {value.mAttributionSourceState} : mAttributionSourceState.next;
             return this;
@@ -759,7 +764,6 @@
             if (value == null) {
                 throw new IllegalArgumentException("Null AttributionSource not permitted.");
             }
-            mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next =
                     new AttributionSourceState[]{value.mAttributionSourceState};
             return this;
@@ -768,28 +772,7 @@
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull AttributionSource build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x40; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x2) == 0) {
-                mAttributionSourceState.pid = Process.INVALID_PID;
-            }
-            if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAttributionSourceState.packageName = null;
-            }
-            if ((mBuilderFieldsSet & 0x8) == 0) {
-                mAttributionSourceState.attributionTag = null;
-            }
-            if ((mBuilderFieldsSet & 0x10) == 0) {
-                mAttributionSourceState.renouncedPermissions = null;
-            }
-            if ((mBuilderFieldsSet & 0x12) == 0) {
-                mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
-            }
-            if ((mBuilderFieldsSet & 0x20) == 0) {
-                mAttributionSourceState.next = null;
-            }
-
-            mAttributionSourceState.token = sDefaultToken;
+            mHasBeenUsed = true;
 
             if (mAttributionSourceState.next == null) {
                 // The NDK aidl backend doesn't support null parcelable arrays.
@@ -799,7 +782,7 @@
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x40) != 0) {
+            if (mHasBeenUsed) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ffcb1cb..3bf0f032 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -106,6 +106,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.compat.IPlatformCompatNative;
+import com.android.internal.protolog.ProtoLogConfigurationService;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -6701,13 +6702,23 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve the
-     * {@link com.android.internal.protolog.ProtoLogService} for registering ProtoLog clients.
+     * {@link ProtoLogConfigurationService} for registering ProtoLog clients.
      *
      * @see #getSystemService(String)
-     * @see com.android.internal.protolog.ProtoLogService
+     * @see ProtoLogConfigurationService
      * @hide
      */
-    public static final String PROTOLOG_SERVICE = "protolog";
+    public static final String PROTOLOG_CONFIGURATION_SERVICE = "protolog_configuration";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.app.supervision.SupervisionManager}.
+     *
+     * @see #getSystemService(String)
+     * @see android.app.supervision.SupervisionManager
+     * @hide
+     */
+    public static final String SUPERVISION_SERVICE = "supervision";
 
     /**
      * Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 7b18117..0a264e3 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -26,6 +26,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
@@ -143,6 +144,7 @@
  * @attr ref android.R.styleable#ColorStateListItem_color
  * @attr ref android.R.styleable#ColorStateListItem_lStar
  */
+@RavenwoodKeepWholeClass
 public class ColorStateList extends ComplexColor implements Parcelable {
     private static final String TAG = "ColorStateList";
 
diff --git a/core/java/android/content/res/ComplexColor.java b/core/java/android/content/res/ComplexColor.java
index 58c6fc5..a385ee3 100644
--- a/core/java/android/content/res/ComplexColor.java
+++ b/core/java/android/content/res/ComplexColor.java
@@ -18,13 +18,14 @@
 
 import android.annotation.ColorInt;
 import android.content.res.Resources.Theme;
-import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 /**
  * Defines an abstract class for the complex color information, like
  * {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor}
  * @hide
  */
+@RavenwoodKeepWholeClass
 public abstract class ComplexColor {
     private int mChangingConfigurations;
 
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index bf4d97d..0559631 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1124,7 +1124,6 @@
      */
     @NonNull
     @Deprecated
-    @RavenwoodThrow(blockedBy = ColorStateList.class)
     public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
         final ColorStateList csl = getColorStateList(id, null);
         if (csl != null && csl.canApplyTheme()) {
@@ -1155,7 +1154,6 @@
      *         color or multiple colors that can be selected based on a state.
      */
     @NonNull
-    @RavenwoodThrow(blockedBy = ColorStateList.class)
     public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
             throws NotFoundException {
         final TypedValue value = obtainTempTypedValue();
@@ -1169,7 +1167,6 @@
     }
 
     @NonNull
-    @RavenwoodThrow(blockedBy = ColorStateList.class)
     ColorStateList loadColorStateList(@NonNull TypedValue value, int id, @Nullable Theme theme)
             throws NotFoundException {
         return mResourcesImpl.loadColorStateList(this, value, id, theme);
@@ -1179,7 +1176,6 @@
      * @hide
      */
     @NonNull
-    @RavenwoodThrow(blockedBy = ComplexColor.class)
     public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, @Nullable Theme theme) {
         return mResourcesImpl.loadComplexColor(this, value, id, theme);
     }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 90420de..e6b9342 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1126,7 +1126,6 @@
     }
 
     @Nullable
-    @RavenwoodThrow(blockedBy = ComplexColor.class)
     ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
             Resources.Theme theme) {
         if (TRACE_FOR_PRELOAD) {
@@ -1168,7 +1167,6 @@
     }
 
     @NonNull
-    @RavenwoodThrow(blockedBy = ColorStateList.class)
     ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
             Resources.Theme theme)
             throws NotFoundException {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f8eeaa9..79185a1 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.os.StrictMode;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -598,7 +597,6 @@
      *         not an integer color or color state list.
      */
     @Nullable
-    @RavenwoodThrow(blockedBy = ColorStateList.class)
     public ColorStateList getColorStateList(@StyleableRes int index) {
         if (mRecycled) {
             throw new RuntimeException("Cannot make calls to a recycled instance!");
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 10c3730..e0b9f60 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -17,7 +17,9 @@
 
 package android.hardware;
 
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputSensorInfo;
 import android.os.Build;
@@ -1182,6 +1184,8 @@
 
     /** @hide */
     @UnsupportedAppUsage
+    @SuppressLint("UnflaggedApi") // Promotion to TestApi
+    @TestApi
     public int getHandle() {
         return mHandle;
     }
diff --git a/core/java/android/hardware/biometrics/AuthenticateOptions.java b/core/java/android/hardware/biometrics/AuthenticateOptions.java
index 7766071..4dc6ea19 100644
--- a/core/java/android/hardware/biometrics/AuthenticateOptions.java
+++ b/core/java/android/hardware/biometrics/AuthenticateOptions.java
@@ -74,4 +74,7 @@
 
     /** The attribution tag, if any. */
     @Nullable String getAttributionTag();
+
+    /** If the authentication is requested due to mandatory biometrics being active. */
+    boolean isMandatoryBiometrics();
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 17cd18c..b195225 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -49,7 +49,7 @@
     void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
             int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
             long requestId, int cookie, boolean allowBackgroundAuthentication,
-            boolean isForLegacyFingerprintManager);
+            boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int cookie);
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 518f902a..8babbfa 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -120,6 +120,8 @@
     }
 
 
+    /** If the authentication is requested due to mandatory biometrics being active. */
+    private boolean mIsMandatoryBiometrics;
 
     // Code below generated by codegen v1.0.23.
     //
@@ -188,7 +190,8 @@
             @AuthenticateReason int authenticateReason,
             @PowerManager.WakeReason int wakeReason,
             @NonNull String opPackageName,
-            @Nullable String attributionTag) {
+            @Nullable String attributionTag,
+            boolean isMandatoryBiometrics) {
         this.mUserId = userId;
         this.mSensorId = sensorId;
         this.mDisplayState = displayState;
@@ -229,6 +232,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mOpPackageName);
         this.mAttributionTag = attributionTag;
+        this.mIsMandatoryBiometrics = isMandatoryBiometrics;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -261,7 +265,7 @@
      * The reason for this operation when requested by the system (sysui),
      * otherwise AUTHENTICATE_REASON_UNKNOWN.
      *
-     * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
+     * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
      * for more details about each reason.
      */
     @DataClass.Generated.Member
@@ -299,6 +303,14 @@
     }
 
     /**
+     * If the authentication is requested due to mandatory biometrics being active.
+     */
+    @DataClass.Generated.Member
+    public boolean isMandatoryBiometrics() {
+        return mIsMandatoryBiometrics;
+    }
+
+    /**
      * The sensor id for this operation.
      */
     @DataClass.Generated.Member
@@ -332,6 +344,15 @@
         return this;
     }
 
+    /**
+     * If the authentication is requested due to mandatory biometrics being active.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FaceAuthenticateOptions setIsMandatoryBiometrics( boolean value) {
+        mIsMandatoryBiometrics = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
@@ -351,7 +372,8 @@
                 && mAuthenticateReason == that.mAuthenticateReason
                 && mWakeReason == that.mWakeReason
                 && java.util.Objects.equals(mOpPackageName, that.mOpPackageName)
-                && java.util.Objects.equals(mAttributionTag, that.mAttributionTag);
+                && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
+                && mIsMandatoryBiometrics == that.mIsMandatoryBiometrics;
     }
 
     @Override
@@ -368,6 +390,7 @@
         _hash = 31 * _hash + mWakeReason;
         _hash = 31 * _hash + java.util.Objects.hashCode(mOpPackageName);
         _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
+        _hash = 31 * _hash + Boolean.hashCode(mIsMandatoryBiometrics);
         return _hash;
     }
 
@@ -377,9 +400,10 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        byte flg = 0;
+        int flg = 0;
+        if (mIsMandatoryBiometrics) flg |= 0x80;
         if (mAttributionTag != null) flg |= 0x40;
-        dest.writeByte(flg);
+        dest.writeInt(flg);
         dest.writeInt(mUserId);
         dest.writeInt(mSensorId);
         dest.writeInt(mDisplayState);
@@ -400,7 +424,8 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        byte flg = in.readByte();
+        int flg = in.readInt();
+        boolean isMandatoryBiometrics = (flg & 0x80) != 0;
         int userId = in.readInt();
         int sensorId = in.readInt();
         int displayState = in.readInt();
@@ -449,6 +474,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mOpPackageName);
         this.mAttributionTag = attributionTag;
+        this.mIsMandatoryBiometrics = isMandatoryBiometrics;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -481,6 +507,7 @@
         private @PowerManager.WakeReason int mWakeReason;
         private @NonNull String mOpPackageName;
         private @Nullable String mAttributionTag;
+        private boolean mIsMandatoryBiometrics;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -524,7 +551,7 @@
          * The reason for this operation when requested by the system (sysui),
          * otherwise AUTHENTICATE_REASON_UNKNOWN.
          *
-         * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
+         * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
          * for more details about each reason.
          */
         @DataClass.Generated.Member
@@ -573,10 +600,21 @@
             return this;
         }
 
+        /**
+         * If the authentication is requested due to mandatory biometrics being active.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setIsMandatoryBiometrics(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mIsMandatoryBiometrics = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull FaceAuthenticateOptions build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mUserId = defaultUserId();
@@ -606,12 +644,13 @@
                     mAuthenticateReason,
                     mWakeReason,
                     mOpPackageName,
-                    mAttributionTag);
+                    mAttributionTag,
+                    mIsMandatoryBiometrics);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -619,10 +658,10 @@
     }
 
     @DataClass.Generated(
-            time = 1677119626034L,
+            time = 1723436679828L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate  boolean mIsMandatoryBiometrics\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
index dc66542..ddf1e5b 100644
--- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
+++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
@@ -97,6 +97,11 @@
         return null;
     }
 
+    /**
+     * If the authentication is requested due to mandatory biometrics being active.
+     */
+    private boolean mIsMandatoryBiometrics;
+
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
@@ -118,7 +123,8 @@
             @AuthenticateOptions.DisplayState int displayState,
             @NonNull String opPackageName,
             @Nullable String attributionTag,
-            @Nullable AuthenticateReason.Vendor vendorReason) {
+            @Nullable AuthenticateReason.Vendor vendorReason,
+            boolean isMandatoryBiometrics) {
         this.mUserId = userId;
         this.mSensorId = sensorId;
         this.mIgnoreEnrollmentState = ignoreEnrollmentState;
@@ -130,6 +136,7 @@
                 NonNull.class, null, mOpPackageName);
         this.mAttributionTag = attributionTag;
         this.mVendorReason = vendorReason;
+        this.mIsMandatoryBiometrics = isMandatoryBiometrics;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -199,6 +206,14 @@
     }
 
     /**
+     * If the authentication is requested due to mandatory biometrics being active.
+     */
+    @DataClass.Generated.Member
+    public boolean isMandatoryBiometrics() {
+        return mIsMandatoryBiometrics;
+    }
+
+    /**
      * The sensor id for this operation.
      */
     @DataClass.Generated.Member
@@ -244,6 +259,15 @@
         return this;
     }
 
+    /**
+     * If the authentication is requested due to mandatory biometrics being active.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FingerprintAuthenticateOptions setIsMandatoryBiometrics( boolean value) {
+        mIsMandatoryBiometrics = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
@@ -263,7 +287,8 @@
                 && mDisplayState == that.mDisplayState
                 && java.util.Objects.equals(mOpPackageName, that.mOpPackageName)
                 && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
-                && java.util.Objects.equals(mVendorReason, that.mVendorReason);
+                && java.util.Objects.equals(mVendorReason, that.mVendorReason)
+                && mIsMandatoryBiometrics == that.mIsMandatoryBiometrics;
     }
 
     @Override
@@ -280,6 +305,7 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mOpPackageName);
         _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
         _hash = 31 * _hash + java.util.Objects.hashCode(mVendorReason);
+        _hash = 31 * _hash + Boolean.hashCode(mIsMandatoryBiometrics);
         return _hash;
     }
 
@@ -289,11 +315,12 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        byte flg = 0;
+        int flg = 0;
         if (mIgnoreEnrollmentState) flg |= 0x4;
+        if (mIsMandatoryBiometrics) flg |= 0x80;
         if (mAttributionTag != null) flg |= 0x20;
         if (mVendorReason != null) flg |= 0x40;
-        dest.writeByte(flg);
+        dest.writeInt(flg);
         dest.writeInt(mUserId);
         dest.writeInt(mSensorId);
         dest.writeInt(mDisplayState);
@@ -313,8 +340,9 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        byte flg = in.readByte();
+        int flg = in.readInt();
         boolean ignoreEnrollmentState = (flg & 0x4) != 0;
+        boolean isMandatoryBiometrics = (flg & 0x80) != 0;
         int userId = in.readInt();
         int sensorId = in.readInt();
         int displayState = in.readInt();
@@ -333,6 +361,7 @@
                 NonNull.class, null, mOpPackageName);
         this.mAttributionTag = attributionTag;
         this.mVendorReason = vendorReason;
+        this.mIsMandatoryBiometrics = isMandatoryBiometrics;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -365,6 +394,7 @@
         private @NonNull String mOpPackageName;
         private @Nullable String mAttributionTag;
         private @Nullable AuthenticateReason.Vendor mVendorReason;
+        private boolean mIsMandatoryBiometrics;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -456,10 +486,21 @@
             return this;
         }
 
+        /**
+         * If the authentication is requested due to mandatory biometrics being active.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setIsMandatoryBiometrics(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x80;
+            mIsMandatoryBiometrics = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull FingerprintAuthenticateOptions build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mUserId = defaultUserId();
@@ -489,12 +530,13 @@
                     mDisplayState,
                     mOpPackageName,
                     mAttributionTag,
-                    mVendorReason);
+                    mVendorReason,
+                    mIsMandatoryBiometrics);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -502,10 +544,10 @@
     }
 
     @DataClass.Generated(
-            time = 1689703591032L,
+            time = 1723436831455L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate @android.annotation.Nullable android.hardware.biometrics.common.AuthenticateReason.Vendor mVendorReason\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nprivate static  android.hardware.biometrics.common.AuthenticateReason.Vendor defaultVendorReason()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate @android.annotation.Nullable android.hardware.biometrics.common.AuthenticateReason.Vendor mVendorReason\nprivate  boolean mIsMandatoryBiometrics\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nprivate static  android.hardware.biometrics.common.AuthenticateReason.Vendor defaultVendorReason()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c7751e3..c4d12d4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1997,6 +1997,8 @@
                 STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG
                 | STATE2_GPS_SIGNAL_QUALITY_MASK;
 
+        public static final int GNSS_SIGNAL_QUALITY_NONE = 2;
+
         @UnsupportedAppUsage
         public int states2;
 
@@ -2220,7 +2222,7 @@
             modemRailChargeMah = 0;
             wifiRailChargeMah = 0;
             states = 0;
-            states2 = 0;
+            states2 = GNSS_SIGNAL_QUALITY_NONE << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
             wakelockTag = null;
             wakeReasonTag = null;
             eventCode = EVENT_NONE;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b7556df..4bc3dbe 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -708,9 +708,16 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
     public final native void markVintfStability();
 
+    /** @hide */
+    private void markVintfStability$ravenwood() {
+        // This is not useful for Ravenwood which uses local binder.
+        // TODO(b/361785059): Use real native libbinder.
+    }
+
     /**
      * Use a VINTF-stability binder w/o VINTF requirements. Should be called
      * on a binder before it is sent out of process.
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 92b630f..bd81fb9 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -46,13 +46,13 @@
  * a {@link Message} object containing a bundle of data that will be
  * processed by the Handler's {@link #handleMessage} method (requiring that
  * you implement a subclass of Handler).
- * 
+ *
  * <p>When posting or sending to a Handler, you can either
  * allow the item to be processed as soon as the message queue is ready
  * to do so, or specify a delay before it gets processed or absolute time for
  * it to be processed.  The latter two allow you to implement timeouts,
  * ticks, and other timing-based behavior.
- * 
+ *
  * <p>When a
  * process is created for your application, its main thread is dedicated to
  * running a message queue that takes care of managing the top-level
@@ -85,13 +85,13 @@
          */
         boolean handleMessage(@NonNull Message msg);
     }
-    
+
     /**
      * Subclasses must implement this to receive messages.
      */
     public void handleMessage(@NonNull Message msg) {
     }
-    
+
     /**
      * Handle system messages here.
      */
@@ -343,8 +343,8 @@
      * The default implementation will either return the class name of the
      * message callback if any, or the hexadecimal representation of the
      * message "what" field.
-     *  
-     * @param message The message whose name is being queried 
+     *
+     * @param message The message whose name is being queried
      */
     @NonNull
     public String getMessageName(@NonNull Message message) {
@@ -367,7 +367,7 @@
 
     /**
      * Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message.
-     * 
+     *
      * @param what Value to assign to the returned Message.what field.
      * @return A Message from the global message pool.
      */
@@ -376,12 +376,12 @@
     {
         return Message.obtain(this, what);
     }
-    
+
     /**
-     * 
-     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
+     *
+     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members
      * of the returned Message.
-     * 
+     *
      * @param what Value to assign to the returned Message.what field.
      * @param obj Value to assign to the returned Message.obj field.
      * @return A Message from the global message pool.
@@ -392,7 +392,7 @@
     }
 
     /**
-     * 
+     *
      * Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned
      * Message.
      * @param what Value to assign to the returned Message.what field.
@@ -405,10 +405,10 @@
     {
         return Message.obtain(this, what, arg1, arg2);
     }
-    
+
     /**
-     * 
-     * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the 
+     *
+     * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the
      * returned Message.
      * @param what Value to assign to the returned Message.what field.
      * @param arg1 Value to assign to the returned Message.arg1 field.
@@ -423,19 +423,19 @@
 
     /**
      * Causes the Runnable r to be added to the message queue.
-     * The runnable will be run on the thread to which this handler is 
-     * attached. 
-     *  
+     * The runnable will be run on the thread to which this handler is
+     * attached.
+     *
      * @param r The Runnable that will be executed.
-     * 
-     * @return Returns true if the Runnable was successfully placed in to the 
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
     public final boolean post(@NonNull Runnable r) {
        return  sendMessageDelayed(getPostMessage(r), 0);
     }
-    
+
     /**
      * Causes the Runnable r to be added to the message queue, to be run
      * at a specific time given by <var>uptimeMillis</var>.
@@ -446,8 +446,8 @@
      * @param r The Runnable that will be executed.
      * @param uptimeMillis The absolute time at which the callback should run,
      *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
-     *  
-     * @return Returns true if the Runnable was successfully placed in to the 
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.  Note that a
      *         result of true does not mean the Runnable will be processed -- if
@@ -457,7 +457,7 @@
     public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
         return sendMessageAtTime(getPostMessage(r), uptimeMillis);
     }
-    
+
     /**
      * Causes the Runnable r to be added to the message queue, to be run
      * at a specific time given by <var>uptimeMillis</var>.
@@ -470,21 +470,21 @@
      *         {@link #removeCallbacksAndMessages}.
      * @param uptimeMillis The absolute time at which the callback should run,
      *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
-     * 
-     * @return Returns true if the Runnable was successfully placed in to the 
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.  Note that a
      *         result of true does not mean the Runnable will be processed -- if
      *         the looper is quit before the delivery time of the message
      *         occurs then the message will be dropped.
-     *         
+     *
      * @see android.os.SystemClock#uptimeMillis
      */
     public final boolean postAtTime(
             @NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
         return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
     }
-    
+
     /**
      * Causes the Runnable r to be added to the message queue, to be run
      * after the specified amount of time elapses.
@@ -492,12 +492,12 @@
      * is attached.
      * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
      * Time spent in deep sleep will add an additional delay to execution.
-     *  
+     *
      * @param r The Runnable that will be executed.
      * @param delayMillis The delay (in milliseconds) until the Runnable
      *        will be executed.
-     *        
-     * @return Returns true if the Runnable was successfully placed in to the 
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.  Note that a
      *         result of true does not mean the Runnable will be processed --
@@ -507,7 +507,7 @@
     public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
         return sendMessageDelayed(getPostMessage(r), delayMillis);
     }
-    
+
     /** @hide */
     public final boolean postDelayed(Runnable r, int what, long delayMillis) {
         return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
@@ -547,10 +547,10 @@
      * <b>This method is only for use in very special circumstances -- it
      * can easily starve the message queue, cause ordering problems, or have
      * other unexpected side-effects.</b>
-     *  
+     *
      * @param r The Runnable that will be executed.
-     * 
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -635,8 +635,8 @@
      * Pushes a message onto the end of the message queue after all pending messages
      * before the current time. It will be received in {@link #handleMessage},
      * in the thread attached to this handler.
-     *  
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -646,8 +646,8 @@
 
     /**
      * Sends a Message containing only the what value.
-     *  
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -659,9 +659,9 @@
     /**
      * Sends a Message containing only the what value, to be delivered
      * after the specified amount of time elapses.
-     * @see #sendMessageDelayed(android.os.Message, long) 
-     * 
-     * @return Returns true if the message was successfully placed in to the 
+     * @see #sendMessageDelayed(android.os.Message, long)
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -672,11 +672,11 @@
     }
 
     /**
-     * Sends a Message containing only the what value, to be delivered 
+     * Sends a Message containing only the what value, to be delivered
      * at a specific time.
      * @see #sendMessageAtTime(android.os.Message, long)
-     *  
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -691,8 +691,8 @@
      * Enqueue a message into the message queue after all pending messages
      * before (current time + delayMillis). You will receive it in
      * {@link #handleMessage}, in the thread attached to this handler.
-     *  
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.  Note that a
      *         result of true does not mean the message will be processed -- if
@@ -713,12 +713,12 @@
      * Time spent in deep sleep will add an additional delay to execution.
      * You will receive it in {@link #handleMessage}, in the thread attached
      * to this handler.
-     * 
+     *
      * @param uptimeMillis The absolute time at which the message should be
      *         delivered, using the
      *         {@link android.os.SystemClock#uptimeMillis} time-base.
-     *         
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.  Note that a
      *         result of true does not mean the message will be processed -- if
@@ -743,8 +743,8 @@
      * <b>This method is only for use in very special circumstances -- it
      * can easily starve the message queue, cause ordering problems, or have
      * other unexpected side-effects.</b>
-     *  
-     * @return Returns true if the message was successfully placed in to the 
+     *
+     * @return Returns true if the message was successfully placed in to the
      *         message queue.  Returns false on failure, usually because the
      *         looper processing the message queue is exiting.
      */
@@ -889,7 +889,7 @@
     }
 
     // if we can get rid of this method, the handler need not remember its loop
-    // we could instead export a getMessageQueue() method... 
+    // we could instead export a getMessageQueue() method...
     @NonNull
     public final Looper getLooper() {
         return mLooper;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index bb74a3e..398140d 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,6 +18,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1170,11 +1171,27 @@
         return removedSubsCount.get() == subscriptionInfos.size();
     }
 
-    /** {@hide} */
-    public static void rebootPromptAndWipeUserData(Context context, String reason)
+    /**
+     * Reboot into recovery and prompt for wiping the device.
+     *
+     * This is used as last resort in case the device is not recoverable using
+     * other recovery steps. This first checks if fs-checkpoint is available, in
+     * which case we commit the checkpoint, otherwise it performs the reboot in
+     * recovery mode and shows user prompt for wiping the device.
+     *
+     * @param context      the context to use.
+     * @param reason       the reason to wipe.
+     *
+     * @throws IOException if something goes wrong.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
+    public static void rebootPromptAndWipeUserData(@NonNull Context context, @NonNull String reason)
             throws IOException {
         boolean checkpointing = false;
-        boolean needReboot = false;
         IVold vold = null;
         try {
             vold = IVold.Stub.asInterface(ServiceManager.checkService("vold"));
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index c801fabf..a15b3bb 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -379,6 +379,12 @@
         private int mReferenceCount;
 
         private MemoryRegistration(int size) {
+            // Round up to the nearest page size
+            final int PAGE_SIZE = OsConstants._SC_PAGE_SIZE;
+            final int remainder = size % PAGE_SIZE;
+            if (remainder != 0) {
+                size += PAGE_SIZE - remainder;
+            }
             mSize = size;
             mReferenceCount = 1;
             VMRuntime.getRuntime().registerNativeAllocation(mSize);
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 23bd30a..0ed1ab6 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -314,6 +314,15 @@
     }
 
     /**
+     * @see #currentNetworkTimeMillis(ITimeDetectorService)
+     * @hide
+     */
+    public static long currentNetworkTimeMillis() {
+        return currentNetworkTimeMillis(ITimeDetectorService.Stub
+                .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE)));
+    }
+
+    /**
      * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
      * using a remote network source outside the device.
      * <p>
@@ -331,14 +340,14 @@
      * at any time. Due to network delays, variations between servers, or local
      * (client side) clock drift, the accuracy of the returned times cannot be
      * guaranteed. In extreme cases, consecutive calls to {@link
-     * #currentNetworkTimeMillis()} could return times that are out of order.
+     * #currentNetworkTimeMillis(ITimeDetectorService)} could return times that
+     * are out of order.
      *
      * @throws DateTimeException when no network time can be provided.
      * @hide
      */
-    public static long currentNetworkTimeMillis() {
-        ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
-                .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+    public static long currentNetworkTimeMillis(
+            ITimeDetectorService timeDetectorService) {
         if (timeDetectorService != null) {
             UnixEpochTime time;
             try {
@@ -380,16 +389,21 @@
      * at any time. Due to network delays, variations between servers, or local
      * (client side) clock drift, the accuracy of the returned times cannot be
      * guaranteed. In extreme cases, consecutive calls to {@link
-     * Clock#millis()} on the returned {@link Clock}could return times that are
+     * Clock#millis()} on the returned {@link Clock} could return times that are
      * out of order.
      *
      * @throws DateTimeException when no network time can be provided.
      */
     public static @NonNull Clock currentNetworkTimeClock() {
         return new SimpleClock(ZoneOffset.UTC) {
+            private ITimeDetectorService mSvc;
             @Override
             public long millis() {
-                return SystemClock.currentNetworkTimeMillis();
+                if (mSvc == null) {
+                    mSvc = ITimeDetectorService.Stub
+                            .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+                }
+                return SystemClock.currentNetworkTimeMillis(mSvc);
             }
         };
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 536eca6..f1ec0e4e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1978,7 +1978,6 @@
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
-    @FlaggedApi(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED)
     public static final String DISALLOW_SIM_GLOBALLY =
             "no_sim_globally";
 
@@ -1999,7 +1998,6 @@
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
-    @FlaggedApi(android.app.admin.flags.Flags.FLAG_ASSIST_CONTENT_USER_RESTRICTION_ENABLED)
     public static final String DISALLOW_ASSIST_CONTENT = "no_assist_content";
 
     /**
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
index a15d9bc..b317b80 100644
--- a/core/java/android/permission/TEST_MAPPING
+++ b/core/java/android/permission/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsPermissionTestCases",
-            "options": [
-                {
-                    "include-filter": "android.permission.cts.PermissionControllerTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.RuntimePermissionPresentationInfoTest"
-                }
-            ]
+            "name": "CtsPermissionTestCases_Platform"
         }
     ],
     "postsubmit": [
@@ -36,4 +28,4 @@
             ]
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 85d2325..24f52d0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19666,6 +19666,8 @@
             public static final int PAIRED_DEVICE_OS_TYPE_ANDROID = 1;
             /** @hide */
             public static final int PAIRED_DEVICE_OS_TYPE_IOS = 2;
+            /** @hide */
+            public static final int PAIRED_DEVICE_OS_TYPE_NONE = 3;
 
             /**
              * The bluetooth settings selected BLE role for the companion.
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 013ec5f..711c414 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -51,11 +51,14 @@
      */
     private Executor mExecutor;
 
+    private Boolean mCurrentRedirectToWake;
+
     // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
     // requests to the {@link DreamOverlayService}
     private static class OverlayClient extends IDreamOverlayClient.Stub {
         private final WeakReference<DreamOverlayService> mService;
         private boolean mShowComplications;
+        private boolean mIsPreview;
         private ComponentName mDreamComponent;
         IDreamOverlayCallback mDreamOverlayCallback;
 
@@ -73,9 +76,11 @@
 
         @Override
         public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
-                String dreamComponent, boolean shouldShowComplications) throws RemoteException {
+                String dreamComponent, boolean isPreview, boolean shouldShowComplications)
+                throws RemoteException {
             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
             mShowComplications = shouldShowComplications;
+            mIsPreview = isPreview;
             mDreamOverlayCallback = callback;
             applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
         }
@@ -122,6 +127,10 @@
             return mShowComplications;
         }
 
+        private boolean isDreamInPreviewMode() {
+            return mIsPreview;
+        }
+
         private ComponentName getComponent() {
             return mDreamComponent;
         }
@@ -132,6 +141,10 @@
         mExecutor.execute(() -> {
             endDreamInternal(mCurrentClient);
             mCurrentClient = client;
+            if (Flags.dreamWakeRedirect() && mCurrentRedirectToWake != null) {
+                mCurrentClient.redirectWake(mCurrentRedirectToWake);
+            }
+
             onStartDream(params);
         });
     }
@@ -282,8 +295,10 @@
             return;
         }
 
+        mCurrentRedirectToWake = redirect;
+
         if (mCurrentClient == null) {
-            throw new IllegalStateException("redirected wake with no dream present");
+            return;
         }
 
         mCurrentClient.redirectWake(redirect);
@@ -295,7 +310,6 @@
      *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_DREAM_WAKE_REDIRECT)
     public void onWakeRequested() {
     }
 
@@ -312,6 +326,19 @@
     }
 
     /**
+     * Returns whether dream is in preview mode.
+     */
+    @FlaggedApi(Flags.FLAG_PUBLISH_PREVIEW_STATE_TO_OVERLAY)
+    public final boolean isDreamInPreviewMode() {
+        if (mCurrentClient == null) {
+            throw new IllegalStateException(
+                    "requested if preview when no dream active");
+        }
+
+        return mCurrentClient.isDreamInPreviewMode();
+    }
+
+    /**
      * Returns the active dream component.
      * @hide
      */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 0242de0..ce31e1e 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -81,6 +81,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.function.Consumer;
 
 /**
@@ -1261,7 +1262,7 @@
     @Override
     public final IBinder onBind(Intent intent) {
         if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
-        mDreamServiceWrapper = new DreamServiceWrapper();
+        mDreamServiceWrapper = new DreamServiceWrapper(new WeakReference<>(this));
         final ComponentName overlayComponent = intent.getParcelableExtra(
                 EXTRA_DREAM_OVERLAY_COMPONENT, ComponentName.class);
 
@@ -1631,7 +1632,8 @@
             i.setComponent(mInjector.getDreamActivityComponent());
             i.setPackage(mInjector.getDreamPackageName());
             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-            DreamActivity.setCallback(i, new DreamActivityCallbacks(mDreamToken));
+            DreamActivity.setCallback(i,
+                    new DreamActivityCallbacks(mDreamToken, new WeakReference<>(this)));
             final ServiceInfo serviceInfo = mInjector.getServiceInfo();
             final CharSequence title = fetchDreamLabel(mInjector.getPackageManager(),
                     mInjector.getResources(), serviceInfo, isPreviewMode);
@@ -1699,6 +1701,7 @@
                                 try {
                                     overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
                                             mDreamComponent.flattenToString(),
+                                            mPreviewMode,
                                             mShouldShowComplications);
                                 } catch (RemoteException e) {
                                     Log.e(mTag, "could not send window attributes:" + e);
@@ -1845,22 +1848,37 @@
      * uses it to control the DreamService. It is also used to receive callbacks from the
      * DreamActivity.
      */
-    final class DreamServiceWrapper extends IDreamService.Stub {
+    static final class DreamServiceWrapper extends IDreamService.Stub {
+        final WeakReference<DreamService> mService;
+
+        DreamServiceWrapper(WeakReference<DreamService> service) {
+            mService = service;
+        }
+
+        private void post(Consumer<DreamService> consumer) {
+            final DreamService service = mService.get();
+
+            if (service == null) {
+                return;
+            }
+
+            service.mHandler.post(() -> consumer.accept(service));
+        }
+
         @Override
         public void attach(final IBinder dreamToken, final boolean canDoze,
                 final boolean isPreviewMode, IRemoteCallback started) {
-            mHandler.post(
-                    () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
+            post(dreamService -> dreamService.attach(dreamToken, canDoze, isPreviewMode, started));
         }
 
         @Override
         public void detach() {
-            mHandler.post(DreamService.this::detach);
+            post(DreamService::detach);
         }
 
         @Override
         public void wakeUp() {
-            mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/));
+            post(dreamService -> dreamService.wakeUp(true /*fromSystem*/));
         }
 
         @Override
@@ -1868,48 +1886,70 @@
             if (!dreamHandlesBeingObscured()) {
                 return;
             }
-
-            mHandler.post(DreamService.this::comeToFront);
+            post(DreamService::comeToFront);
         }
     }
 
+    private void onActivityCreated(DreamActivity activity, IBinder dreamToken) {
+        if (dreamToken != mDreamToken || mFinished) {
+            Slog.d(TAG, "DreamActivity was created after the dream was finished or "
+                    + "a new dream started, finishing DreamActivity");
+            if (!activity.isFinishing()) {
+                activity.finishAndRemoveTask();
+            }
+            return;
+        }
+        if (mActivity != null) {
+            Slog.w(TAG, "A DreamActivity has already been started, "
+                    + "finishing latest DreamActivity");
+            if (!activity.isFinishing()) {
+                activity.finishAndRemoveTask();
+            }
+            return;
+        }
+
+        mActivity = activity;
+        onWindowCreated(activity.getWindow());
+    }
+
+    private void onActivityDestroyed() {
+        mActivity = null;
+        mWindow = null;
+        detach();
+    }
+
     /** @hide */
     @VisibleForTesting
-    public final class DreamActivityCallbacks extends Binder {
+    public static final class DreamActivityCallbacks extends Binder {
         private final IBinder mActivityDreamToken;
+        private WeakReference<DreamService> mService;
 
-        DreamActivityCallbacks(IBinder token) {
+        DreamActivityCallbacks(IBinder token, WeakReference<DreamService> service)  {
             mActivityDreamToken = token;
+            mService = service;
         }
 
         /** Callback when the {@link DreamActivity} has been created */
         public void onActivityCreated(DreamActivity activity) {
-            if (mActivityDreamToken != mDreamToken || mFinished) {
-                Slog.d(TAG, "DreamActivity was created after the dream was finished or "
-                        + "a new dream started, finishing DreamActivity");
-                if (!activity.isFinishing()) {
-                    activity.finishAndRemoveTask();
-                }
-                return;
-            }
-            if (mActivity != null) {
-                Slog.w(TAG, "A DreamActivity has already been started, "
-                        + "finishing latest DreamActivity");
-                if (!activity.isFinishing()) {
-                    activity.finishAndRemoveTask();
-                }
+            final DreamService service = mService.get();
+
+            if (service == null) {
                 return;
             }
 
-            mActivity = activity;
-            onWindowCreated(activity.getWindow());
+            service.onActivityCreated(activity, mActivityDreamToken);
         }
 
         /** Callback when the {@link DreamActivity} has been destroyed */
         public void onActivityDestroyed() {
-            mActivity = null;
-            mWindow = null;
-            detach();
+            final DreamService service = mService.get();
+
+            if (service == null) {
+                return;
+            }
+
+            service.onActivityDestroyed();
+            mService = null;
         }
     }
 
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
index 0eb15a0..e9df402 100644
--- a/core/java/android/service/dreams/IDreamOverlayClient.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -31,11 +31,12 @@
     * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
     *                dream.
     * @param dreamComponent The component name of the dream service requesting overlay.
+    * @param isPreview Whether the dream is in preview mode.
     * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
     *                and weather.
     */
     void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
-        in String dreamComponent, in boolean shouldShowComplications);
+        in String dreamComponent, in boolean isPreview, in boolean shouldShowComplications);
 
     /** Called when the dream is waking, to do any exit animations */
     void wakeUp();
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 83e0adf..72f2de8 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -57,3 +57,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "publish_preview_state_to_overlay"
+    namespace: "systemui"
+    description: "send preview information from dream to overlay"
+    bug: "333734282"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 57acc71..918e591 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -241,10 +241,11 @@
     // ZenModeConfig XML versions distinguishing key changes.
     public static final int XML_VERSION_ZEN_UPGRADE = 8;
     public static final int XML_VERSION_MODES_API = 11;
+    public static final int XML_VERSION_MODES_UI = 12;
 
-    // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when
-    //       modes_api is inlined.
-    private static final int XML_VERSION = 10;
+    // TODO: b/310620812, b/344831624 - Update XML_VERSION and update default_zen_config.xml
+    //  accordingly when modes_api / modes_ui are inlined.
+    private static final int XML_VERSION_PRE_MODES = 10;
     public static final String ZEN_TAG = "zen";
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ZEN_ATT_USER = "user";
@@ -952,7 +953,13 @@
     }
 
     public static int getCurrentXmlVersion() {
-        return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION;
+        if (Flags.modesUi()) {
+            return XML_VERSION_MODES_UI;
+        } else if (Flags.modesApi()) {
+            return XML_VERSION_MODES_API;
+        } else {
+            return XML_VERSION_PRE_MODES;
+        }
     }
 
     public static ZenModeConfig readXml(TypedXmlPullParser parser)
@@ -2607,7 +2614,7 @@
         @AutomaticZenRule.Type
         public int type = AutomaticZenRule.TYPE_UNKNOWN;
         public String triggerDescription;
-        public String iconResName;
+        @Nullable public String iconResName;
         public boolean allowManualInvocation;
         @AutomaticZenRule.ModifiableField public int userModifiedFields;
         @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 17adb32..25f321e 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
 import com.android.internal.R;
 
 /**
@@ -34,6 +36,7 @@
  * and not have static methods here but there is some concern about
  * performance since these methods are called during view drawing.
  */
+@RavenwoodKeepWholeClass
 public class StateSet {
     /**
      * The order here is very important to
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1f7ed8b..82c52a6 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -314,6 +314,8 @@
      * @hide
      * @see #getFlags()
      */
+    @SuppressLint("UnflaggedApi") // Promotion to TestApi
+    @TestApi
     public static final int FLAG_ALWAYS_UNLOCKED = 1 << 9;
 
     /**
@@ -323,6 +325,8 @@
      * @hide
      * @see #getFlags()
      */
+    @SuppressLint("UnflaggedApi") // Promotion to TestApi
+    @TestApi
     public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
 
     /**
@@ -336,6 +340,8 @@
      * @see #FLAG_TRUSTED
      * @hide
      */
+    @SuppressLint("UnflaggedApi") // Promotion to TestApi
+    @TestApi
     public static final int FLAG_OWN_FOCUS = 1 << 11;
 
     /**
@@ -642,6 +648,8 @@
      * @hide
      */
     // TODO (b/114338689): Remove the flag and use WindowManager#REMOVE_CONTENT_MODE_DESTROY
+    @SuppressLint("UnflaggedApi") // Promotion to TestApi
+    @TestApi
     public static final int REMOVE_MODE_DESTROY_CONTENT = 1;
 
     /** @hide */
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8912035..ab9bd1f 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -730,13 +730,13 @@
 
         /* The distance between point (x, y) and rect, there are 2 basic cases:
          * a) The distance is the distance from (x, y) to the closest corner on rect.
-         *                    o |     |
+         *          o |     |
          *         ---+-----+---
          *            |     |
          *         ---+-----+---
          *            |     |
          * b) The distance is the distance from (x, y) to the closest edge on rect.
-         *                      |  o  |
+         *            |  o  |
          *         ---+-----+---
          *            |     |
          *         ---+-----+---
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6568912..91e9230 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -35,8 +35,8 @@
 import static android.view.InsetsController.LayoutInsetsDuringAnimation;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsSource.SIDE_BOTTOM;
-import static android.view.InsetsSource.SIDE_NONE;
 import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_NONE;
 import static android.view.InsetsSource.SIDE_RIGHT;
 import static android.view.InsetsSource.SIDE_TOP;
 import static android.view.WindowInsets.Type.ime;
@@ -100,6 +100,8 @@
     private @InsetsType int mControllingTypes;
     private final InsetsAnimationControlCallbacks mController;
     private final WindowInsetsAnimation mAnimation;
+    private final long mDurationMs;
+    private final Interpolator mInterpolator;
     /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
     private final boolean mHasZeroInsetsIme;
     private final CompatibilityInfo.Translator mTranslator;
@@ -120,8 +122,8 @@
     @VisibleForTesting
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
             @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
-            @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
-            Interpolator interpolator, @AnimationType int animationType,
+            @InsetsType int types, InsetsAnimationControlCallbacks controller,
+            InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
         mControls = controls;
@@ -155,8 +157,10 @@
         }
         mPendingInsets = mCurrentInsets;
 
-        mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
-                durationMs);
+        mDurationMs = insetsAnimationSpec.getDurationMs(mHasZeroInsetsIme);
+        mInterpolator = insetsAnimationSpec.getInsetsInterpolator(mHasZeroInsetsIme);
+
+        mAnimation = new WindowInsetsAnimation(mTypes, mInterpolator, mDurationMs);
         mAnimation.setAlpha(getCurrentAlpha());
         mAnimationType = animationType;
         mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
@@ -186,6 +190,16 @@
     }
 
     @Override
+    public long getDurationMs() {
+        return mDurationMs;
+    }
+
+    @Override
+    public Interpolator getInsetsInterpolator() {
+        return mInterpolator;
+    }
+
+    @Override
     public void setReadyDispatched(boolean dispatched) {
         mReadyDispatched = dispatched;
     }
diff --git a/core/java/android/view/InsetsAnimationSpec.java b/core/java/android/view/InsetsAnimationSpec.java
new file mode 100644
index 0000000..7ad6661
--- /dev/null
+++ b/core/java/android/view/InsetsAnimationSpec.java
@@ -0,0 +1,39 @@
+/*
+ * 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.view;
+
+import android.view.animation.Interpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Used by {@link InsetsAnimationControlImpl}
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public interface InsetsAnimationSpec {
+    /**
+     * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+     * @return Duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     */
+    long getDurationMs(boolean hasZeroInsetsIme);
+    /**
+     * @param hasZeroInsetsIme whether IME has no insets (floating, fullscreen or non-overlapping).
+     * @return The interpolator used for the animation
+     */
+    Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme);
+}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 1b3b3eb..fc185bc 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -33,7 +33,6 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
-import android.view.animation.Interpolator;
 import android.view.inputmethod.ImeTracker;
 
 /**
@@ -110,15 +109,15 @@
     @UiThread
     public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
             @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
-            @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
-            Interpolator interpolator, @AnimationType int animationType,
+            @InsetsType int types, InsetsAnimationControlCallbacks controller,
+            InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             CompatibilityInfo.Translator translator, Handler mainThreadHandler,
             @Nullable ImeTracker.Token statsToken) {
         mMainThreadHandler = mainThreadHandler;
         mOuterCallbacks = controller;
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
-                mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+                mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
                 translator, statsToken);
         InsetsAnimationThread.getHandler().post(() -> {
             if (mControl.isCancelled()) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7896cbd..7c8cd93 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -366,7 +366,7 @@
      * animate insets.
      */
     public static class InternalAnimationControlListener
-            implements WindowInsetsAnimationControlListener {
+            implements WindowInsetsAnimationControlListener, InsetsAnimationSpec {
 
         private WindowInsetsAnimationController mController;
         private ValueAnimator mAnimator;
@@ -374,7 +374,6 @@
         private final boolean mHasAnimationCallbacks;
         private final @InsetsType int mRequestedTypes;
         private final @Behavior int mBehavior;
-        private final long mDurationMs;
         private final boolean mDisable;
         private final int mFloatingImeBottomInset;
         private final WindowInsetsAnimationControlListener mLoggingListener;
@@ -388,7 +387,6 @@
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
             mBehavior = behavior;
-            mDurationMs = calculateDurationMs();
             mDisable = disable;
             mFloatingImeBottomInset = floatingImeBottomInset;
             mLoggingListener = loggingListener;
@@ -407,13 +405,14 @@
                 onAnimationFinish();
                 return;
             }
+            final boolean hasZeroInsetsIme = controller.hasZeroInsetsIme();
             mAnimator = ValueAnimator.ofFloat(0f, 1f);
-            mAnimator.setDuration(mDurationMs);
+            mAnimator.setDuration(controller.getDurationMs());
             mAnimator.setInterpolator(new LinearInterpolator());
             Insets hiddenInsets = controller.getHiddenStateInsets();
             // IME with zero insets is a special case: it will animate-in from offscreen and end
             // with final insets of zero and vice-versa.
-            hiddenInsets = controller.hasZeroInsetsIme()
+            hiddenInsets = hasZeroInsetsIme
                     ? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
                             mFloatingImeBottomInset)
                     : hiddenInsets;
@@ -423,7 +422,7 @@
             Insets end = mShow
                     ? controller.getShownStateInsets()
                     : hiddenInsets;
-            Interpolator insetsInterpolator = getInsetsInterpolator();
+            Interpolator insetsInterpolator = controller.getInsetsInterpolator();
             Interpolator alphaInterpolator = getAlphaInterpolator();
             mAnimator.addUpdateListener(animation -> {
                 float rawFraction = animation.getAnimatedFraction();
@@ -486,9 +485,10 @@
             }
         }
 
-        protected Interpolator getInsetsInterpolator() {
+        @Override
+        public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
             if ((mRequestedTypes & ime()) != 0) {
-                if (mHasAnimationCallbacks) {
+                if (mHasAnimationCallbacks && !hasZeroInsetsIme) {
                     return SYNC_IME_INTERPOLATOR;
                 } else if (mShow) {
                     return LINEAR_OUT_SLOW_IN_INTERPOLATOR;
@@ -507,10 +507,9 @@
 
         Interpolator getAlphaInterpolator() {
             if ((mRequestedTypes & ime()) != 0) {
-                if (mHasAnimationCallbacks) {
+                if (mHasAnimationCallbacks && !mController.hasZeroInsetsIme()) {
                     return input -> 1f;
                 } else if (mShow) {
-
                     // Alpha animation takes half the time with linear interpolation;
                     return input -> Math.min(1f, 2 * input);
                 } else {
@@ -534,16 +533,10 @@
             if (DEBUG) Log.d(TAG, "onAnimationFinish showOnFinish: " + mShow);
         }
 
-        /**
-         * To get the animation duration in MS.
-         */
-        public long getDurationMs() {
-            return mDurationMs;
-        }
-
-        private long calculateDurationMs() {
+        @Override
+        public long getDurationMs(boolean hasZeroInsetsIme) {
             if ((mRequestedTypes & ime()) != 0) {
-                if (mHasAnimationCallbacks) {
+                if (mHasAnimationCallbacks && !hasZeroInsetsIme) {
                     return ANIMATION_DURATION_SYNC_IME_MS;
                 } else {
                     return ANIMATION_DURATION_UNSYNC_IME_MS;
@@ -593,13 +586,13 @@
     private static class PendingControlRequest {
 
         PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener,
-                long durationMs, Interpolator interpolator, @AnimationType int animationType,
+                InsetsAnimationSpec insetsAnimationSpec,
+                @AnimationType int animationType,
                 @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
                 CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) {
             this.types = types;
             this.listener = listener;
-            this.durationMs = durationMs;
-            this.interpolator = interpolator;
+            this.mInsetsAnimationSpec = insetsAnimationSpec;
             this.animationType = animationType;
             this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
             this.cancellationSignal = cancellationSignal;
@@ -608,8 +601,7 @@
 
         @InsetsType int types;
         final WindowInsetsAnimationControlListener listener;
-        final long durationMs;
-        final Interpolator interpolator;
+        final InsetsAnimationSpec mInsetsAnimationSpec;
         final @AnimationType int animationType;
         final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation;
         final CancellationSignal cancellationSignal;
@@ -1201,12 +1193,10 @@
 
         // We are about to playing the default animation. Passing a null frame indicates the
         // controlled types should be animated regardless of the frame.
-        controlAnimationUnchecked(
-                pendingRequest.types, pendingRequest.cancellationSignal,
-                pendingRequest.listener, null /* frame */,
-                true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
-                pendingRequest.animationType,
-                pendingRequest.layoutInsetsDuringAnimation,
+        controlAnimationUnchecked(pendingRequest.types, pendingRequest.cancellationSignal,
+                pendingRequest.listener, null /* frame */, true /* fromIme */,
+                pendingRequest.mInsetsAnimationSpec,
+                pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
                 pendingRequest.useInsetsAnimationThread, statsToken);
     }
 
@@ -1327,18 +1317,26 @@
                     mHost.getInputMethodManager(), null /* icProto */);
         }
 
+        InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+            @Override
+            public long getDurationMs(boolean hasZeroInsetsIme) {
+                return durationMs;
+            }
+            @Override
+            public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+                return interpolator;
+            }
+        };
         // TODO(b/342111149): Create statsToken here once ImeTracker#onStart becomes async.
-        controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
-                interpolator, animationType,
-                getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
+        controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec,
+                animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
                 false /* useInsetsAnimationThread */, null);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
             @Nullable CancellationSignal cancellationSignal,
             WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
-            long durationMs, Interpolator interpolator,
-            @AnimationType int animationType,
+            InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
         final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -1349,7 +1347,7 @@
         // However, we might reject the request in some cases, such as delaying showing IME or
         // rejecting showing IME.
         controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
-                durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+                insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
                 useInsetsAnimationThread, statsToken);
 
         // We are finishing setting the requested visible types. Report them to the server
@@ -1360,8 +1358,7 @@
     private void controlAnimationUncheckedInner(@InsetsType int types,
             @Nullable CancellationSignal cancellationSignal,
             WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
-            long durationMs, Interpolator interpolator,
-            @AnimationType int animationType,
+            InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
         if ((types & mTypesBeingCancelled) != 0) {
@@ -1418,8 +1415,8 @@
                     // TODO (b/323319146) remove layoutInsetsDuringAnimation from
                     //  PendingControlRequest, as it is now only used for showing
                     final PendingControlRequest request = new PendingControlRequest(types,
-                            listener, durationMs,
-                            interpolator, animationType, LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+                            listener, insetsAnimationSpec, animationType,
+                            LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
                             cancellationSignal, false /* useInsetsAnimationThread */);
                     mPendingImeControlRequest = request;
                     // only add a timeout when the control is not currently showing
@@ -1460,11 +1457,9 @@
             if (!imeReady) {
                 // IME isn't ready, all requested types will be animated once IME is ready
                 abortPendingImeControlRequest();
-                final PendingControlRequest request = new PendingControlRequest(types,
-                        listener, durationMs,
-                        interpolator, animationType, layoutInsetsDuringAnimation,
-                        cancellationSignal,
-                        useInsetsAnimationThread);
+                final PendingControlRequest request = new PendingControlRequest(types, listener,
+                        insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+                        cancellationSignal, useInsetsAnimationThread);
                 mPendingImeControlRequest = request;
                 mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
                 if (DEBUG) Log.d(TAG, "Ime not ready. Create pending request");
@@ -1520,11 +1515,11 @@
 
         final InsetsAnimationControlRunner runner = useInsetsAnimationThread
                 ? new InsetsAnimationThreadControlRunner(controls,
-                        frame, mState, listener, typesReady, this, durationMs, interpolator,
-                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
-                        mHost.getHandler(), statsToken)
+                        frame, mState, listener, typesReady, this,
+                        insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+                        mHost.getTranslator(), mHost.getHandler(), statsToken)
                 : new InsetsAnimationControlImpl(controls,
-                        frame, mState, listener, typesReady, this, durationMs, interpolator,
+                        frame, mState, listener, typesReady, this, insetsAnimationSpec,
                         animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
                         statsToken);
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
@@ -2023,7 +2018,7 @@
         // the controlled types should be animated regardless of the frame.
         controlAnimationUnchecked(
                 types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
-                listener.getDurationMs(), listener.getInsetsInterpolator(),
+                listener /* insetsAnimationSpec */,
                 show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
                 !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index 6e62221..f90b841 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -233,6 +233,16 @@
     }
 
     @Override
+    public long getDurationMs() {
+        return 0;
+    }
+
+    @Override
+    public Interpolator getInsetsInterpolator() {
+        return null;
+    }
+
+    @Override
     public void setReadyDispatched(boolean dispatched) {
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 477e35b..391d757 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -310,21 +310,22 @@
         }
         final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
 
+        // If we don't have control or the leash (in case of the IME), we enforce the
+        // visibility to be hidden, as otherwise we would let the app know too early.
+        if (mSourceControl == null) {
+            if (DEBUG) {
+                Log.d(TAG, TextUtils.formatSimple(
+                        "applyLocalVisibilityOverride: No control in %s for type %s, "
+                                + "requestedVisible=%s",
+                        mController.getHost().getRootViewTitle(),
+                        WindowInsets.Type.toString(mType), requestedVisible));
+            }
+            return false;
+        }
         if (Flags.refactorInsetsController()) {
-            // If we don't have control or the leash (in case of the IME), we enforce the
-            // visibility to be hidden, as otherwise we would let the app know too early.
-            if (mSourceControl == null) {
-                if (DEBUG) {
-                    Log.d(TAG, TextUtils.formatSimple(
-                            "applyLocalVisibilityOverride: No control in %s for type %s, "
-                                    + "requestedVisible=%s",
-                            mController.getHost().getRootViewTitle(),
-                            WindowInsets.Type.toString(mType), requestedVisible));
-                }
-                return false;
-                // TODO(b/323136120) add a flag to the control, to define whether a leash is needed
-            } else if (mId != InsetsSource.ID_IME_CAPTION_BAR
-                    && mSourceControl.getLeash() == null) {
+            // TODO(b/323136120) add a flag to the control, to define whether a leash is
+            //  needed and make it generic for all types
+            if (mId == InsetsSource.ID_IME && mSourceControl.getLeash() == null) {
                 if (DEBUG) {
                     Log.d(TAG, TextUtils.formatSimple(
                             "applyLocalVisibilityOverride: Set the source visibility to false, as"
@@ -338,16 +339,6 @@
                 // changed state
                 return wasVisible;
             }
-        } else {
-            // If we don't have control, we are not able to change the visibility.
-            if (mSourceControl == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "applyLocalVisibilityOverride: No control in "
-                            + mController.getHost().getRootViewTitle()
-                            + " requestedVisible=" + requestedVisible);
-                }
-                return false;
-            }
         }
         if (source.isVisible() == requestedVisible) {
             return false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 9e4b27d..2dda835 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -445,16 +445,20 @@
         // Jank due to unknown reasons.
         public static final int UNKNOWN = 0x80;
 
-        public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs) {
+        public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
+                long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
             this.frameVsyncId = frameVsyncId;
             this.jankType = jankType;
             this.frameIntervalNs = frameIntervalNs;
-
+            this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
+            this.actualAppFrameTimeNs = actualAppFrameTimeNs;
         }
 
         public final long frameVsyncId;
         public final @JankType int jankType;
         public final long frameIntervalNs;
+        public final long scheduledAppFrameTimeNs;
+        public final long actualAppFrameTimeNs;
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6f88386..3b5286a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3093,74 +3093,74 @@
      */
     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
             View child, int desiredPointerIdBits) {
-        final boolean handled;
-
-        // Canceling motions is a special case.  We don't need to perform any transformations
-        // or filtering.  The important part is the action, not the contents.
         final int oldAction = event.getAction();
-        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
-            event.setAction(MotionEvent.ACTION_CANCEL);
-            if (child == null) {
-                handled = super.dispatchTouchEvent(event);
-            } else {
-                handled = child.dispatchTouchEvent(event);
+        try {
+            final boolean handled;
+            if (cancel) {
+                event.setAction(MotionEvent.ACTION_CANCEL);
             }
-            event.setAction(oldAction);
-            return handled;
-        }
 
-        // Calculate the number of pointers to deliver.
-        final int oldPointerIdBits = event.getPointerIdBits();
-        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
+            // Calculate the number of pointers to deliver.
+            final int oldPointerIdBits = event.getPointerIdBits();
+            int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
 
-        // If for some reason we ended up in an inconsistent state where it looks like we
-        // might produce a motion event with no pointers in it, then drop the event.
-        if (newPointerIdBits == 0) {
-            return false;
-        }
-
-        // If the number of pointers is the same and we don't need to perform any fancy
-        // irreversible transformations, then we can reuse the motion event for this
-        // dispatch as long as we are careful to revert any changes we make.
-        // Otherwise we need to make a copy.
-        final MotionEvent transformedEvent;
-        if (newPointerIdBits == oldPointerIdBits) {
-            if (child == null || child.hasIdentityMatrix()) {
-                if (child == null) {
-                    handled = super.dispatchTouchEvent(event);
+            // If for some reason we ended up in an inconsistent state where it looks like we
+            // might produce a non-cancel motion event with no pointers in it, then drop the event.
+            // Make sure that we don't drop any cancel events.
+            if (newPointerIdBits == 0) {
+                if (event.getAction() != MotionEvent.ACTION_CANCEL) {
+                    return false;
                 } else {
-                    final float offsetX = mScrollX - child.mLeft;
-                    final float offsetY = mScrollY - child.mTop;
-                    event.offsetLocation(offsetX, offsetY);
-
-                    handled = child.dispatchTouchEvent(event);
-
-                    event.offsetLocation(-offsetX, -offsetY);
+                    newPointerIdBits = oldPointerIdBits;
                 }
-                return handled;
-            }
-            transformedEvent = MotionEvent.obtain(event);
-        } else {
-            transformedEvent = event.split(newPointerIdBits);
-        }
-
-        // Perform any necessary transformations and dispatch.
-        if (child == null) {
-            handled = super.dispatchTouchEvent(transformedEvent);
-        } else {
-            final float offsetX = mScrollX - child.mLeft;
-            final float offsetY = mScrollY - child.mTop;
-            transformedEvent.offsetLocation(offsetX, offsetY);
-            if (! child.hasIdentityMatrix()) {
-                transformedEvent.transform(child.getInverseMatrix());
             }
 
-            handled = child.dispatchTouchEvent(transformedEvent);
-        }
+            // If the number of pointers is the same and we don't need to perform any fancy
+            // irreversible transformations, then we can reuse the motion event for this
+            // dispatch as long as we are careful to revert any changes we make.
+            // Otherwise we need to make a copy.
+            final MotionEvent transformedEvent;
+            if (newPointerIdBits == oldPointerIdBits) {
+                if (child == null || child.hasIdentityMatrix()) {
+                    if (child == null) {
+                        handled = super.dispatchTouchEvent(event);
+                    } else {
+                        final float offsetX = mScrollX - child.mLeft;
+                        final float offsetY = mScrollY - child.mTop;
+                        event.offsetLocation(offsetX, offsetY);
 
-        // Done.
-        transformedEvent.recycle();
-        return handled;
+                        handled = child.dispatchTouchEvent(event);
+
+                        event.offsetLocation(-offsetX, -offsetY);
+                    }
+                    return handled;
+                }
+                transformedEvent = MotionEvent.obtain(event);
+            } else {
+                transformedEvent = event.split(newPointerIdBits);
+            }
+
+            // Perform any necessary transformations and dispatch.
+            if (child == null) {
+                handled = super.dispatchTouchEvent(transformedEvent);
+            } else {
+                final float offsetX = mScrollX - child.mLeft;
+                final float offsetY = mScrollY - child.mTop;
+                transformedEvent.offsetLocation(offsetX, offsetY);
+                if (!child.hasIdentityMatrix()) {
+                    transformedEvent.transform(child.getInverseMatrix());
+                }
+
+                handled = child.dispatchTouchEvent(transformedEvent);
+            }
+
+            // Done.
+            transformedEvent.recycle();
+            return handled;
+
+        } finally {
+            event.setAction(oldAction);
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0e02627..0e1625a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4388,14 +4388,7 @@
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
             mActiveSurfaceSyncGroup = null;
-            if (mHasPendingTransactions) {
-                // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't
-                // merged with a sync group or BLASTBufferQueue before making it to this point
-                // But better a one or two frame flicker than steady-state broken from dropping
-                // whatever is in this transaction
-                mPendingTransaction.apply();
-                mHasPendingTransactions = false;
-            }
+            mHasPendingTransactions = false;
             mSyncBuffer = false;
             if (isInWMSRequestedSync()) {
                 mWmsRequestSyncGroup.markSyncReady();
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 6578e9b..d3ea982 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -23,6 +23,7 @@
 import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
+import android.view.animation.Interpolator;
 
 /**
  * Controller for app-driven animation of system windows.
@@ -188,4 +189,16 @@
      *  fullscreen or non-overlapping).
      */
     boolean hasZeroInsetsIme();
+
+    /**
+     * @hide
+     * @return The duration of the animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+     */
+    long getDurationMs();
+
+    /**
+     * @hide
+     * @return The interpolator of the animation.
+     */
+    Interpolator getInsetsInterpolator();
 }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index a5ba294..fe6aafb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,6 +982,7 @@
     private long mParentNodeId = UNDEFINED_NODE_ID;
     private long mLabelForId = UNDEFINED_NODE_ID;
     private long mLabeledById = UNDEFINED_NODE_ID;
+    private LongArray mLabeledByIds;
     private long mTraversalBefore = UNDEFINED_NODE_ID;
     private long mTraversalAfter = UNDEFINED_NODE_ID;
 
@@ -3599,6 +3600,133 @@
     }
 
     /**
+     * Adds the view which serves as the label of the view represented by
+     * this info for accessibility purposes. When multiple labels are
+     * added, the content from each label is combined in the order that
+     * they are added.
+     * <p>
+     * If visible text can be used to describe or give meaning to this UI,
+     * this method is preferred. For example, a TextView before an EditText
+     * in the UI usually specifies what information is contained in the
+     * EditText. Hence, the EditText is labeled by the TextView.
+     * </p>
+     *
+     * @param label A view that labels this node's source.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public void addLabeledBy(@NonNull View label) {
+        addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Adds the view which serves as the label of the view represented by
+     * this info for accessibility purposes. If <code>virtualDescendantId</code>
+     * is {@link View#NO_ID} the root is set as the label. When multiple
+     * labels are added, the content from each label is combined in the order
+     * that they are added.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     * <p>
+     * If visible text can be used to describe or give meaning to this UI,
+     * this method is preferred. For example, a TextView before an EditText
+     * in the UI usually specifies what information is contained in the
+     * EditText. Hence, the EditText is labeled by the TextView.
+     * </p>
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param root A root whose virtual descendant labels this node's source.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
+        enforceNotSealed();
+        Preconditions.checkNotNull(root, "%s must not be null", root);
+        if (mLabeledByIds == null) {
+            mLabeledByIds = new LongArray();
+        }
+        mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
+        mLabeledByIds.add(mLabeledById);
+    }
+
+    /**
+     * Gets the list of node infos which serve as the labels of the view represented by
+     * this info for accessibility purposes.
+     *
+     * @return The list of labels in the order that they were added.
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
+        enforceSealed();
+        List<AccessibilityNodeInfo> labels = new ArrayList<>();
+        if (mLabeledByIds == null) {
+            return labels;
+        }
+        for (int i = 0; i < mLabeledByIds.size(); i++) {
+            labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
+        }
+        return labels;
+    }
+
+    /**
+     * Removes a label. If the label was not previously added to the node,
+     * calling this method has no effect.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param label The node which serves as this node's label.
+     * @return true if the label was present
+     * @see #addLabeledBy(View)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public boolean removeLabeledBy(@NonNull View label) {
+        return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+    }
+
+    /**
+     * Removes a label which is a virtual descendant of the given
+     * <code>root</code>. If <code>virtualDescendantId</code> is
+     * {@link View#NO_ID} the root is set as the label. If the label
+     * was not previously added to the node, calling this method has
+     * no effect.
+     *
+     * @param root The root of the virtual subtree.
+     * @param virtualDescendantId The id of the virtual node which serves as this node's label.
+     * @return true if the label was present
+     * @see #addLabeledBy(View, int)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
+    public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
+        enforceNotSealed();
+        final LongArray labeledByIds = mLabeledByIds;
+        if (labeledByIds == null) {
+            return false;
+        }
+        final int rootAccessibilityViewId =
+                (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        if (mLabeledById == labeledById) {
+            mLabeledById = UNDEFINED_NODE_ID;
+        }
+        final int index = labeledByIds.indexOf(labeledById);
+        if (index < 0) {
+            return false;
+        }
+        labeledByIds.remove(index);
+        return true;
+    }
+
+    /**
      * Sets the view which serves as the label of the view represented by
      * this info for accessibility purposes.
      *
@@ -3631,7 +3759,17 @@
         enforceNotSealed();
         final int rootAccessibilityViewId = (root != null)
                 ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+        if (Flags.supportMultipleLabeledby()) {
+            if (mLabeledByIds == null) {
+                mLabeledByIds = new LongArray();
+            } else {
+                mLabeledByIds.clear();
+            }
+        }
         mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+        if (Flags.supportMultipleLabeledby()) {
+            mLabeledByIds.add(mLabeledById);
+        }
     }
 
     /**
@@ -4242,6 +4380,10 @@
         fieldIndex++;
         if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
+        if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
         if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,18 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            final LongArray labeledByIds = mLabeledByIds;
+            if (labeledByIds == null) {
+                parcel.writeInt(0);
+            } else {
+                final int labeledByIdsSize = labeledByIds.size();
+                parcel.writeInt(labeledByIdsSize);
+                for (int i = 0; i < labeledByIdsSize; i++) {
+                    parcel.writeLong(labeledByIds.get(i));
+                }
+            }
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4704,7 @@
         mParentNodeId = other.mParentNodeId;
         mLabelForId = other.mLabelForId;
         mLabeledById = other.mLabeledById;
+        mLabeledByIds = other.mLabeledByIds;
         mTraversalBefore = other.mTraversalBefore;
         mTraversalAfter = other.mTraversalAfter;
         mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4811,18 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            final int labeledByIdsSize = parcel.readInt();
+            if (labeledByIdsSize <= 0) {
+                mLabeledByIds = null;
+            } else {
+                mLabeledByIds = new LongArray(labeledByIdsSize);
+                for (int i = 0; i < labeledByIdsSize; i++) {
+                    final long labeledById = parcel.readLong();
+                    mLabeledByIds.add(labeledById);
+                }
+            }
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b281015..23d7732 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1365,10 +1365,10 @@
                                         ImeTracker.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY);
                                 if (visible) {
                                     insetsController.show(WindowInsets.Type.ime(),
-                                            false /* fromIme */, null /* statsToken */);
+                                            false /* fromIme */, statsToken);
                                 } else {
                                     insetsController.hide(WindowInsets.Type.ime(),
-                                            false /* fromIme */, null /* statsToken */);
+                                            false /* fromIme */, statsToken);
                                 }
                             }
                         } else {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index fe26510..7366b9a 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -497,25 +497,32 @@
     public abstract  boolean getUseWebViewBackgroundForOverscrollBackground();
 
     /**
-     * Sets whether the WebView should save form data. In Android O, the
-     * platform has implemented a fully functional Autofill feature to store
-     * form data. Therefore, the Webview form data save feature is disabled.
+     * Sets whether the WebView should save form data. In {@link android.os.Build.VERSION_CODES#O},
+     * the platform has implemented a fully functional Autofill feature to store form data.
+     * Therefore, the Webview form data save feature is disabled.
      *
-     * Note that the feature will continue to be supported on older versions of
+     * <p>Note that the feature will continue to be supported on older versions of
      * Android as before.
      *
-     * @deprecated In Android O and afterwards, this function does not have
-     * any effect, the form data will be saved to platform's autofill service
-     * if applicable.
+     * @see #getSaveFormData
+     * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+     * will be saved to platform's autofill service if applicable.
      */
     @Deprecated
     public abstract  void setSaveFormData(boolean save);
 
     /**
-     * Gets whether the WebView saves form data.
+     * Gets whether the WebView saves form data. In {@link android.os.Build.VERSION_CODES#O}, the
+     * platform has implemented a fully functional Autofill feature to store form data. Therefore,
+     * the Webview form data save feature is disabled.
+     *
+     * <p>Note that the feature will continue to be supported on older versions of
+     * Android as before.
      *
      * @return whether the WebView saves form data
      * @see #setSaveFormData
+     * @deprecated In Android O and afterwards, this function does not have any effect. Form data
+     * will be filled from the platform's autofill service if applicable.
      */
     @Deprecated
     public abstract boolean getSaveFormData();
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 9931aea..ac5656d 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -289,8 +289,7 @@
 
     private synchronized void updateText(long now) {
         mNow = now;
-        long seconds = mCountDown ? mBase - now : now - mBase;
-        seconds /= 1000;
+        long seconds = Math.round((mCountDown ? mBase - now - 499 : now - mBase) / 1000f);
         boolean negative = false;
         if (seconds < 0) {
             seconds = -seconds;
@@ -348,9 +347,19 @@
     };
 
     private void postTickOnNextSecond() {
-        long nowMillis = SystemClock.elapsedRealtime();
-        int millis = (int) ((nowMillis - mBase) % 1000);
-        postDelayed(mTickRunnable, 1000 - millis);
+        long nowMillis = mNow;
+        long delayMillis;
+        if (mCountDown) {
+            delayMillis = (mBase - nowMillis) % 1000;
+            if (delayMillis <= 0) {
+                delayMillis += 1000;
+            }
+        } else {
+            delayMillis = 1000 - (Math.abs(nowMillis - mBase) % 1000);
+        }
+        // Aim for 1 millisecond into the next second so we don't update exactly on the second
+        delayMillis++;
+        postDelayed(mTickRunnable, delayMillis);
     }
 
     void dispatchChronometerTick() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 03a2672..0acc6bd 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -20,6 +20,7 @@
 import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;
 
 import static com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect;
+import static com.android.text.flags.Flags.contextMenuHideUnavailableItems;
 
 import android.R;
 import android.animation.ValueAnimator;
@@ -3250,62 +3251,135 @@
         final int menuItemOrderShare = 9;
         final int menuItemOrderAutofill = 10;
 
-        menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
-                com.android.internal.R.string.undo)
-                .setAlphabeticShortcut('z')
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
-                .setIcon(a.getDrawable(0))
-                .setEnabled(mTextView.canUndo());
-        menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
-                com.android.internal.R.string.redo)
-                .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
-                .setIcon(a.getDrawable(1))
-                .setEnabled(mTextView.canRedo());
+        if (contextMenuHideUnavailableItems()) {
+            if (mTextView.canUndo()) {
+                menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
+                                com.android.internal.R.string.undo)
+                        .setAlphabeticShortcut('z')
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                        .setIcon(a.getDrawable(0));
+            }
 
-        menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
-                com.android.internal.R.string.cut)
-                .setAlphabeticShortcut('x')
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
-                .setIcon(a.getDrawable(2))
-                .setEnabled(mTextView.canCut());
-        menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
-                com.android.internal.R.string.copy)
-                .setAlphabeticShortcut('c')
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
-                .setIcon(a.getDrawable(3))
-                .setEnabled(mTextView.canCopy());
-        menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
-                com.android.internal.R.string.paste)
-                .setAlphabeticShortcut('v')
-                .setEnabled(mTextView.canPaste())
-                .setIcon(a.getDrawable(4))
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
-        menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
-                        menuItemOrderPasteAsPlainText,
-                com.android.internal.R.string.paste_as_plain_text)
-                .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
-                .setEnabled(mTextView.canPasteAsPlainText())
-                .setIcon(a.getDrawable(4))
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
-        menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
-                        menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
-                .setAlphabeticShortcut('a')
-                .setEnabled(mTextView.canSelectAllText())
-                .setIcon(a.getDrawable(5))
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            if (mTextView.canRedo()) {
+                menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
+                                com.android.internal.R.string.redo)
+                        .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                        .setIcon(a.getDrawable(1));
+            }
 
-        menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
-                com.android.internal.R.string.share)
-                .setEnabled(mTextView.canShare())
-                .setIcon(a.getDrawable(6))
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
-        final String selected = mTextView.getSelectedText();
-        menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
-                android.R.string.autofill)
-                .setEnabled(mTextView.canRequestAutofill()
-                        && (selected == null || selected.isEmpty()))
-                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            if (mTextView.canCut()) {
+                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
+                                com.android.internal.R.string.cut)
+                        .setAlphabeticShortcut('x')
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                        .setIcon(a.getDrawable(2));
+            }
+
+            if (mTextView.canCopy()) {
+                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
+                                com.android.internal.R.string.copy)
+                        .setAlphabeticShortcut('c')
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                        .setIcon(a.getDrawable(3));
+            }
+
+            if (mTextView.canPaste()) {
+                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
+                                com.android.internal.R.string.paste)
+                        .setAlphabeticShortcut('v')
+                        .setIcon(a.getDrawable(4))
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            }
+
+            if (mTextView.canPasteAsPlainText()) {
+                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
+                                menuItemOrderPasteAsPlainText,
+                                com.android.internal.R.string.paste_as_plain_text)
+                        .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
+                        .setIcon(a.getDrawable(4))
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            }
+
+            if (mTextView.canSelectAllText()) {
+                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
+                                menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
+                        .setAlphabeticShortcut('a')
+                        .setIcon(a.getDrawable(5))
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            }
+
+            if (mTextView.canShare()) {
+                menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
+                                com.android.internal.R.string.share)
+                        .setIcon(a.getDrawable(6))
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            }
+
+            final String selected = mTextView.getSelectedText();
+            if (mTextView.canRequestAutofill() && (selected == null || selected.isEmpty())) {
+                menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
+                                android.R.string.autofill)
+                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            }
+        } else {
+            menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
+                            com.android.internal.R.string.undo)
+                    .setAlphabeticShortcut('z')
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                    .setIcon(a.getDrawable(0))
+                    .setEnabled(mTextView.canUndo());
+            menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
+                            com.android.internal.R.string.redo)
+                    .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                    .setIcon(a.getDrawable(1))
+                    .setEnabled(mTextView.canRedo());
+
+            menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
+                            com.android.internal.R.string.cut)
+                    .setAlphabeticShortcut('x')
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                    .setIcon(a.getDrawable(2))
+                    .setEnabled(mTextView.canCut());
+            menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
+                            com.android.internal.R.string.copy)
+                    .setAlphabeticShortcut('c')
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+                    .setIcon(a.getDrawable(3))
+                    .setEnabled(mTextView.canCopy());
+            menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
+                            com.android.internal.R.string.paste)
+                    .setAlphabeticShortcut('v')
+                    .setEnabled(mTextView.canPaste())
+                    .setIcon(a.getDrawable(4))
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
+                            menuItemOrderPasteAsPlainText,
+                            com.android.internal.R.string.paste_as_plain_text)
+                    .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
+                    .setEnabled(mTextView.canPasteAsPlainText())
+                    .setIcon(a.getDrawable(4))
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
+                            menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
+                    .setAlphabeticShortcut('a')
+                    .setEnabled(mTextView.canSelectAllText())
+                    .setIcon(a.getDrawable(5))
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+
+            menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
+                            com.android.internal.R.string.share)
+                    .setEnabled(mTextView.canShare())
+                    .setIcon(a.getDrawable(6))
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+            final String selected = mTextView.getSelectedText();
+            menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
+                            android.R.string.autofill)
+                    .setEnabled(mTextView.canRequestAutofill()
+                            && (selected == null || selected.isEmpty()))
+                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+        }
         a.recycle();
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 72b268b..a346a67 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10627,7 +10627,7 @@
 
         int startOffset = mLayout.getOffsetForHorizontal(line, point.x);
         if (mLayout.isLevelBoundary(startOffset)) {
-            // TODO(b/247551937): Support gesture at level boundaries.
+            // Gesture at level boundaries is not supported.
             return handleGestureFailure(gesture);
         }
 
@@ -15552,15 +15552,21 @@
         }
     }
 
-    boolean canUndo() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canUndo() {
         return mEditor != null && mEditor.canUndo();
     }
 
-    boolean canRedo() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canRedo() {
         return mEditor != null && mEditor.canRedo();
     }
 
-    boolean canCut() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canCut() {
         if (hasPasswordTransformationMethod()) {
             return false;
         }
@@ -15573,7 +15579,9 @@
         return false;
     }
 
-    boolean canCopy() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canCopy() {
         if (hasPasswordTransformationMethod()) {
             return false;
         }
@@ -15594,7 +15602,9 @@
                 && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
     }
 
-    boolean canShare() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canShare() {
         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
                 || !getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_textShareSupported)) {
@@ -15613,8 +15623,10 @@
         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
     }
 
+    /** @hide */
+    @VisibleForTesting
     @UnsupportedAppUsage
-    boolean canPaste() {
+    public boolean canPaste() {
         return (mText instanceof Editable
                 && mEditor != null && mEditor.mKeyListener != null
                 && getSelectionStart() >= 0
@@ -15622,7 +15634,9 @@
                 && getClipboardManagerForUser().hasPrimaryClip());
     }
 
-    boolean canPasteAsPlainText() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canPasteAsPlainText() {
         if (!canPaste()) {
             return false;
         }
@@ -15644,7 +15658,9 @@
         return canShare();
     }
 
-    boolean canSelectAllText() {
+    /** @hide */
+    @VisibleForTesting
+    public boolean canSelectAllText() {
         return canSelectText() && !hasPasswordTransformationMethod()
                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
     }
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
index b1d7582..a801776 100644
--- a/core/java/android/window/IBackAnimationRunner.aidl
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -38,14 +38,13 @@
     /**
      * Called when the system is ready for the handler to start animating all the visible tasks.
      * @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
-                   windows to animate,
-     * @param wallpapers The list of wallpapers to animate.
-     * @param nonApps The list of non-app windows such as Bubbles to animate.
+     *             windows to animate,
+     * @param prepareOpenTransition If non-null, the animation should start after receive open
+     *             transition
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
     void onAnimationStart(
             in RemoteAnimationTarget[] apps,
-            in RemoteAnimationTarget[] wallpapers,
-            in RemoteAnimationTarget[] nonApps,
+            in IBinder prepareOpenTransition,
             in IBackAnimationFinishedCallback finishedCallback) = 2;
 }
\ No newline at end of file
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 205f1de..2f595d1 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -59,7 +59,6 @@
 import android.util.Log;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -185,7 +184,6 @@
 
         private void drawSizeMismatchSnapshot() {
             final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
-            final SurfaceSession session = new SurfaceSession();
 
             // We consider nearly matched dimensions as there can be rounding errors and the user
             // won't notice very minute differences from scaling one dimension more than the other
@@ -193,7 +191,7 @@
                     && !Flags.drawSnapshotAspectRatioMatch();
 
             // Keep a reference to it such that it doesn't get destroyed when finalized.
-            SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
+            SurfaceControl childSurfaceControl = new SurfaceControl.Builder()
                     .setName(mTitle + " - task-snapshot-surface")
                     .setBLASTLayer()
                     .setFormat(buffer.getFormat())
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1083f64..ec79f94 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1141,6 +1141,7 @@
         // Customize activity transition animation
         private CustomActivityTransition mCustomActivityOpenTransition;
         private CustomActivityTransition mCustomActivityCloseTransition;
+        private int mUserId;
 
         private AnimationOptions(int type) {
             mType = type;
@@ -1159,6 +1160,7 @@
             mAnimations = in.readInt();
             mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
             mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
+            mUserId = in.readInt();
         }
 
         /** Make basic customized animation for a package */
@@ -1283,6 +1285,14 @@
             return options;
         }
 
+        public void setUserId(int userId) {
+            mUserId = userId;
+        }
+
+        public int getUserId() {
+            return mUserId;
+        }
+
         public int getType() {
             return mType;
         }
@@ -1349,6 +1359,7 @@
             dest.writeInt(mAnimations);
             dest.writeTypedObject(mCustomActivityOpenTransition, flags);
             dest.writeTypedObject(mCustomActivityCloseTransition, flags);
+            dest.writeInt(mUserId);
         }
 
         @NonNull
@@ -1406,6 +1417,7 @@
             if (mExitResId != DEFAULT_ANIMATION_RESOURCES_ID) {
                 sb.append(" exitResId=").append(mExitResId);
             }
+            sb.append(" mUserId=").append(mUserId);
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 4f84817..ebf87f1 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -115,6 +115,16 @@
 }
 
 flag {
+    name: "respect_orientation_change_for_unresizeable"
+    namespace: "lse_desktop_experience"
+    description: "Whether to resize task to respect requested orientation change of unresizeable activity"
+    bug: "353338503"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_camera_compat_for_desktop_windowing"
     namespace: "lse_desktop_experience"
     description: "Whether to apply Camera Compat treatment to fixed-orientation apps in desktop windowing mode"
@@ -225,3 +235,13 @@
     description: "Adds a minimize button the the caption bar"
     bug: "356843241"
 }
+
+flag {
+    name: "skip_compat_ui_education_in_desktop_mode"
+    namespace: "lse_desktop_experience"
+    description: "Ignore Compat UI educations when in Desktop Mode."
+    bug: "357062954"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 8c6721a..efacc34 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -49,3 +49,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "avoid_rebinding_intentionally_disconnected_wallpaper"
+  namespace: "systemui"
+  description: "Prevents rebinding with intentionally disconnected wallpaper services."
+  bug: "332871851"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index be744fd..a786fc2 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,16 +19,6 @@
 }
 
 flag {
-    name: "do_not_skip_ime_by_target_visibility"
-    namespace: "windowing_frontend"
-    description: "Avoid window traversal missing IME"
-    bug: "339375944"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "apply_lifecycle_on_pip_change"
     namespace: "windowing_frontend"
     description: "Make pip activity lifecyle change with windowing mode"
@@ -256,3 +246,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "always_capture_activity_snapshot"
+  namespace: "windowing_frontend"
+  description: "Always capture activity snapshot regardless predictive back status"
+  bug: "362183912"
+  is_fixed_read_only: true
+  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 4ccdf79..cf3a54b 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -109,45 +109,13 @@
     public static List<AccessibilityTarget> getInstalledTargets(Context context,
             @UserShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
-        targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
+        targets.addAll(getAccessibilityServiceTargets(context, shortcutType));
+        targets.addAll(getAccessibilityActivityTargets(context, shortcutType));
         targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
 
         return targets;
     }
 
-    private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
-            @UserShortcutType int shortcutType) {
-        final List<AccessibilityTarget> serviceTargets =
-                getAccessibilityServiceTargets(context, shortcutType);
-        final List<AccessibilityTarget> activityTargets =
-                getAccessibilityActivityTargets(context, shortcutType);
-
-        for (AccessibilityTarget activityTarget : activityTargets) {
-            serviceTargets.removeIf(
-                    serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
-        }
-
-        final List<AccessibilityTarget> targets = new ArrayList<>();
-        targets.addAll(serviceTargets);
-        targets.addAll(activityTargets);
-
-        return targets;
-    }
-
-    private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
-            @NonNull AccessibilityTarget activityTarget) {
-        final ComponentName serviceComponentName =
-                ComponentName.unflattenFromString(serviceTarget.getId());
-        final ComponentName activityComponentName =
-                ComponentName.unflattenFromString(activityTarget.getId());
-        final boolean isSamePackageName = activityComponentName.getPackageName().equals(
-                serviceComponentName.getPackageName());
-        final boolean isSameLabel = activityTarget.getLabel().equals(
-                serviceTarget.getLabel());
-
-        return isSamePackageName && isSameLabel;
-    }
-
     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
             @UserShortcutType int shortcutType) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 1c26687..2e0ff3d 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -34,6 +34,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
@@ -176,24 +177,19 @@
      * @param type The shortcut type.
      * @return Mapping key in Settings.
      */
+    @SuppressLint("SwitchIntDef")
     public static String convertToKey(@UserShortcutType int type) {
-        switch (type) {
-            case SOFTWARE:
-                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
-            case GESTURE:
-                return Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
-            case HARDWARE:
-                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
-            case TRIPLETAP:
-                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
-            case TWOFINGER_DOUBLETAP:
-                return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
-            case QUICK_SETTINGS:
-                return Settings.Secure.ACCESSIBILITY_QS_TARGETS;
-            default:
-                throw new IllegalArgumentException(
-                        "Unsupported user shortcut type: " + type);
-        }
+        return switch (type) {
+            case SOFTWARE -> Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+            case GESTURE -> Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
+            case HARDWARE -> Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+            case TRIPLETAP -> Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            case TWOFINGER_DOUBLETAP ->
+                    Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
+            case QUICK_SETTINGS -> Settings.Secure.ACCESSIBILITY_QS_TARGETS;
+            default -> throw new IllegalArgumentException(
+                    "Unsupported user shortcut type: " + type);
+        };
     }
 
     /**
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index e4550c0..35f0553 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -9,15 +9,7 @@
       ]
     },
     {
-      "name": "CtsPermissionTestCases",
-      "options": [
-          {
-            "include-filter": "android.permission.cts.PermissionControllerTest"
-          },
-          {
-            "exclude-annotation": "androidx.test.filters.FlakyTest"
-          }
-      ]
+      "name": "CtsPermissionTestCases_Platform"
     },
     {
       "name": "FrameworksCoreTests_internal_infra"
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 53ef49b..d474c6d 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -127,7 +127,7 @@
     private Runnable mWaitForFinishTimedOut;
 
     private static class JankInfo {
-        long frameVsyncId;
+        final long frameVsyncId;
         long totalDurationNanos;
         boolean isFirstFrame;
         boolean hwuiCallbackFired;
@@ -135,29 +135,42 @@
         @JankType int jankType;
         @RefreshRate int refreshRate;
 
-        static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
-                boolean isFirstFrame) {
-            return new JankInfo(frameVsyncId, true, false, JANK_NONE, UNKNOWN_REFRESH_RATE,
-                    totalDurationNanos, isFirstFrame);
+        static JankInfo createFromHwuiCallback(
+                long frameVsyncId, long totalDurationNanos, boolean isFirstFrame) {
+            return new JankInfo(frameVsyncId).update(totalDurationNanos, isFirstFrame);
         }
 
-        static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
-                @JankType int jankType, @RefreshRate int refreshRate) {
-            return new JankInfo(
-                    frameVsyncId, false, true, jankType, refreshRate, 0, false /* isFirstFrame */);
+        static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) {
+            return new JankInfo(jankStat.frameVsyncId).update(jankStat);
         }
 
-        private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
-                boolean surfaceControlCallbackFired, @JankType int jankType,
-                @RefreshRate int refreshRate,
-                long totalDurationNanos, boolean isFirstFrame) {
+        private JankInfo(long frameVsyncId) {
             this.frameVsyncId = frameVsyncId;
-            this.hwuiCallbackFired = hwuiCallbackFired;
-            this.surfaceControlCallbackFired = surfaceControlCallbackFired;
-            this.jankType = jankType;
-            this.refreshRate = refreshRate;
-            this.totalDurationNanos = totalDurationNanos;
+            this.hwuiCallbackFired = false;
+            this.surfaceControlCallbackFired = false;
+            this.jankType = JANK_NONE;
+            this.refreshRate = UNKNOWN_REFRESH_RATE;
+            this.totalDurationNanos = 0;
+            this.isFirstFrame = false;
+        }
+
+        private JankInfo update(SurfaceControl.JankData jankStat) {
+            this.surfaceControlCallbackFired = true;
+            this.jankType = jankStat.jankType;
+            this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
+            if (Flags.useSfFrameDuration()) {
+                this.totalDurationNanos = jankStat.actualAppFrameTimeNs;
+            }
+            return this;
+        }
+
+        private JankInfo update(long totalDurationNanos, boolean isFirstFrame) {
+            this.hwuiCallbackFired = true;
+            if (!Flags.useSfFrameDuration()) {
+                this.totalDurationNanos = totalDurationNanos;
+            }
             this.isFirstFrame = isFirstFrame;
+            return this;
         }
 
         @Override
@@ -457,16 +470,12 @@
                 if (!isInRange(jankStat.frameVsyncId)) {
                     continue;
                 }
-                int refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
                 JankInfo info = findJankInfo(jankStat.frameVsyncId);
                 if (info != null) {
-                    info.surfaceControlCallbackFired = true;
-                    info.jankType = jankStat.jankType;
-                    info.refreshRate = refreshRate;
+                    info.update(jankStat);
                 } else {
                     mJankInfos.put((int) jankStat.frameVsyncId,
-                            JankInfo.createFromSurfaceControlCallback(
-                                    jankStat.frameVsyncId, jankStat.jankType, refreshRate));
+                            JankInfo.createFromSurfaceControlCallback(jankStat));
                 }
             }
             processJankInfos();
@@ -513,9 +522,7 @@
             }
             JankInfo info = findJankInfo(frameVsyncId);
             if (info != null) {
-                info.hwuiCallbackFired = true;
-                info.totalDurationNanos = totalDurationNanos;
-                info.isFirstFrame = isFirstFrame;
+                info.update(totalDurationNanos, isFirstFrame);
             } else {
                 mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
                         frameVsyncId, totalDurationNanos, isFirstFrame));
diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig
new file mode 100644
index 0000000..676bb70
--- /dev/null
+++ b/core/java/com/android/internal/jank/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.jank"
+container: "system"
+
+flag {
+  name: "use_sf_frame_duration"
+  namespace: "android_platform_window_surfaces"
+  description: "Whether to get the frame duration from SurfaceFlinger, or HWUI"
+  bug: "354763298"
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e14249c..e0529b3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3333,6 +3333,7 @@
             Bundle args = new Bundle();
             args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, event.getDeviceId());
             args.putLong(Intent.EXTRA_TIME, event.getEventTime());
+            args.putBoolean(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, true);
             ((SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE))
                     .launchAssist(args);
             return true;
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 238e6f5..201f267 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -49,7 +49,7 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Handler;
-import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.InflateException;
 import android.view.SurfaceControl;
@@ -187,23 +187,44 @@
         return createHiddenByKeyguardExit(mContext, mInterpolator, onWallpaper, toShade, subtle);
     }
 
+    /** Load keyguard unocclude animation for user. */
+    @Nullable
+    public Animation loadKeyguardUnoccludeAnimation(int userId) {
+        return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit, userId);
+    }
+
+    /** Same as {@code loadKeyguardUnoccludeAnimation} for current user. */
     @Nullable
     public Animation loadKeyguardUnoccludeAnimation() {
-        return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit);
+        return loadKeyguardUnoccludeAnimation(UserHandle.USER_CURRENT);
     }
 
+    /** Load voice activity open animation for user. */
     @Nullable
-    public Animation loadVoiceActivityOpenAnimation(boolean enter) {
+    public Animation loadVoiceActivityOpenAnimation(boolean enter, int userId) {
         return loadDefaultAnimationRes(enter
                 ? com.android.internal.R.anim.voice_activity_open_enter
-                : com.android.internal.R.anim.voice_activity_open_exit);
+                : com.android.internal.R.anim.voice_activity_open_exit, userId);
     }
 
+    /** Same as {@code loadVoiceActivityOpenAnimation} for current user. */
     @Nullable
-    public Animation loadVoiceActivityExitAnimation(boolean enter) {
+    public Animation loadVoiceActivityOpenAnimation(boolean enter) {
+        return loadVoiceActivityOpenAnimation(enter, UserHandle.USER_CURRENT);
+    }
+
+    /** Load voice activity exit animation for user. */
+    @Nullable
+    public Animation loadVoiceActivityExitAnimation(boolean enter, int userId) {
         return loadDefaultAnimationRes(enter
                 ? com.android.internal.R.anim.voice_activity_close_enter
-                : com.android.internal.R.anim.voice_activity_close_exit);
+                : com.android.internal.R.anim.voice_activity_close_exit, userId);
+    }
+
+    /** Same as {@code loadVoiceActivityExitAnimation} for current user. */
+    @Nullable
+    public Animation loadVoiceActivityExitAnimation(boolean enter) {
+        return loadVoiceActivityExitAnimation(enter, UserHandle.USER_CURRENT);
     }
 
     @Nullable
@@ -211,10 +232,17 @@
         return loadAnimationRes(packageName, resId);
     }
 
+    /** Load cross profile app enter animation for user. */
+    @Nullable
+    public Animation loadCrossProfileAppEnterAnimation(int userId) {
+        return loadAnimationRes(DEFAULT_PACKAGE,
+                com.android.internal.R.anim.task_open_enter_cross_profile_apps, userId);
+    }
+
+    /** Same as {@code loadCrossProfileAppEnterAnimation} for current user. */
     @Nullable
     public Animation loadCrossProfileAppEnterAnimation() {
-        return loadAnimationRes(DEFAULT_PACKAGE,
-                com.android.internal.R.anim.task_open_enter_cross_profile_apps);
+        return loadCrossProfileAppEnterAnimation(UserHandle.USER_CURRENT);
     }
 
     @Nullable
@@ -230,11 +258,11 @@
                 appRect.height(), 0, null);
     }
 
-    /** Load animation by resource Id from specific package. */
+    /** Load animation by resource Id from specific package for user. */
     @Nullable
-    public Animation loadAnimationRes(String packageName, int resId) {
+    public Animation loadAnimationRes(String packageName, int resId, int userId) {
         if (ResourceId.isValid(resId)) {
-            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+            AttributeCache.Entry ent = getCachedAnimations(packageName, resId, userId);
             if (ent != null) {
                 return loadAnimationSafely(ent.context, resId, mTag);
             }
@@ -242,10 +270,22 @@
         return null;
     }
 
-    /** Load animation by resource Id from android package. */
+    /** Same as {@code loadAnimationRes} for current user. */
+    @Nullable
+    public Animation loadAnimationRes(String packageName, int resId) {
+        return loadAnimationRes(packageName, resId, UserHandle.USER_CURRENT);
+    }
+
+    /** Load animation by resource Id from android package for user. */
+    @Nullable
+    public Animation loadDefaultAnimationRes(int resId, int userId) {
+        return loadAnimationRes(DEFAULT_PACKAGE, resId, userId);
+    }
+
+    /** Same as {@code loadDefaultAnimationRes} for current user. */
     @Nullable
     public Animation loadDefaultAnimationRes(int resId) {
-        return loadAnimationRes(DEFAULT_PACKAGE, resId);
+        return loadAnimationRes(DEFAULT_PACKAGE, resId, UserHandle.USER_CURRENT);
     }
 
     /** Load animation by attribute Id from specific LayoutParams */
@@ -378,10 +418,10 @@
     }
 
     @Nullable
-    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId, int userId) {
         if (mDebug) {
-            Slog.v(mTag, "Loading animations: package="
-                    + packageName + " resId=0x" + Integer.toHexString(resId));
+            Slog.v(mTag, "Loading animations: package=" + packageName + " resId=0x"
+                    + Integer.toHexString(resId) + " for user=" + userId);
         }
         if (packageName != null) {
             if ((resId & 0xFF000000) == 0x01000000) {
@@ -392,11 +432,16 @@
                         + packageName);
             }
             return AttributeCache.instance().get(packageName, resId,
-                    com.android.internal.R.styleable.WindowAnimation);
+                    com.android.internal.R.styleable.WindowAnimation, userId);
         }
         return null;
     }
 
+    @Nullable
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+        return getCachedAnimations(packageName, resId, UserHandle.USER_CURRENT);
+    }
+
     /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
     public int getAnimationStyleResId(@NonNull LayoutParams lp) {
         int resId = lp.windowAnimations;
diff --git a/core/java/com/android/internal/protolog/IProtoLogClient.aidl b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
index 969ed99..64944f4 100644
--- a/core/java/com/android/internal/protolog/IProtoLogClient.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogClient.aidl
@@ -20,7 +20,7 @@
  * The ProtoLog client interface.
  *
  * These clients will communicate bi-directionally with the ProtoLog service
- * (@see IProtoLogService.aidl) running in the system process.
+ * (@see IProtoLogConfigurationService.aidl) running in the system process.
  *
  * {@hide}
  */
diff --git a/core/java/com/android/internal/protolog/IProtoLogService.aidl b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
similarity index 97%
rename from core/java/com/android/internal/protolog/IProtoLogService.aidl
rename to core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
index cc349ea..ce94828 100644
--- a/core/java/com/android/internal/protolog/IProtoLogService.aidl
+++ b/core/java/com/android/internal/protolog/IProtoLogConfigurationService.aidl
@@ -40,7 +40,7 @@
  *
  * {@hide}
  */
-interface IProtoLogService {
+interface IProtoLogConfigurationService {
     interface IRegisterClientArgs {
         String[] getGroups();
         boolean[] getGroupsDefaultLogcatStatus();
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 5517967..2ff8c8c 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.protolog;
 
-import static android.content.Context.PROTOLOG_SERVICE;
+import static android.content.Context.PROTOLOG_CONFIGURATION_SERVICE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
 import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -114,7 +114,7 @@
     private final Runnable mCacheUpdater;
 
     @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
-    private final IProtoLogService mProtoLogService;
+    private final IProtoLogConfigurationService mProtoLogConfigurationService;
 
     @NonNull
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
@@ -186,30 +186,32 @@
         registerGroupsLocally(groups);
 
         if (android.tracing.Flags.clientSideProtoLogging()) {
-            mProtoLogService =
-                    IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
-            Objects.requireNonNull(mProtoLogService,
-                    "ServiceManager returned a null ProtoLog service");
+            mProtoLogConfigurationService =
+                    IProtoLogConfigurationService.Stub.asInterface(ServiceManager.getService(
+                            PROTOLOG_CONFIGURATION_SERVICE));
+            Objects.requireNonNull(mProtoLogConfigurationService,
+                    "ServiceManager returned a null ProtoLog Configuration Service");
 
             try {
-                var args = new ProtoLogService.RegisterClientArgs();
+                var args = new ProtoLogConfigurationService.RegisterClientArgs();
 
                 if (viewerConfigFilePath != null) {
                     args.setViewerConfigFile(viewerConfigFilePath);
                 }
 
                 final var groupArgs = Stream.of(groups)
-                        .map(group -> new ProtoLogService.RegisterClientArgs.GroupConfig(
-                                group.name(), group.isLogToLogcat()))
-                        .toArray(ProtoLogService.RegisterClientArgs.GroupConfig[]::new);
+                        .map(group -> new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(group.name(), group.isLogToLogcat()))
+                        .toArray(
+                                ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new);
                 args.setGroups(groupArgs);
 
-                mProtoLogService.registerClient(this, args);
+                mProtoLogConfigurationService.registerClient(this, args);
             } catch (RemoteException e) {
                 throw new RuntimeException("Failed to register ProtoLog client");
             }
         } else {
-            mProtoLogService = null;
+            mProtoLogConfigurationService = null;
         }
     }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
index 3dab2e3..82d8d34 100644
--- a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -29,18 +29,20 @@
 
 public class ProtoLogCommandHandler extends ShellCommand {
     @NonNull
-    private final ProtoLogService mProtoLogService;
+    private final ProtoLogConfigurationService mProtoLogConfigurationService;
     @Nullable
     private final PrintWriter mPrintWriter;
 
-    public ProtoLogCommandHandler(@NonNull ProtoLogService protoLogService) {
-        this(protoLogService, null);
+    public ProtoLogCommandHandler(
+            @NonNull ProtoLogConfigurationService protoLogConfigurationService) {
+        this(protoLogConfigurationService, null);
     }
 
     @VisibleForTesting
     public ProtoLogCommandHandler(
-            @NonNull ProtoLogService protoLogService, @Nullable PrintWriter printWriter) {
-        this.mProtoLogService = protoLogService;
+            @NonNull ProtoLogConfigurationService protoLogConfigurationService,
+            @Nullable PrintWriter printWriter) {
+        this.mProtoLogConfigurationService = protoLogConfigurationService;
         this.mPrintWriter = printWriter;
     }
 
@@ -94,7 +96,7 @@
 
         switch (cmd) {
             case "list": {
-                final String[] availableGroups = mProtoLogService.getGroups();
+                final String[] availableGroups = mProtoLogConfigurationService.getGroups();
                 if (availableGroups.length == 0) {
                     pw.println("No ProtoLog groups registered with ProtoLog service.");
                     return 0;
@@ -117,12 +119,13 @@
 
                 pw.println("ProtoLog group " + group + "'s status:");
 
-                if (!Set.of(mProtoLogService.getGroups()).contains(group)) {
+                if (!Set.of(mProtoLogConfigurationService.getGroups()).contains(group)) {
                     pw.println("UNREGISTERED");
                     return 0;
                 }
 
-                pw.println("LOG_TO_LOGCAT = " + mProtoLogService.isLoggingToLogcat(group));
+                pw.println("LOG_TO_LOGCAT = "
+                        + mProtoLogConfigurationService.isLoggingToLogcat(group));
                 return 0;
             }
             default: {
@@ -142,11 +145,11 @@
 
         switch (cmd) {
             case "enable" -> {
-                mProtoLogService.enableProtoLogToLogcat(processGroups());
+                mProtoLogConfigurationService.enableProtoLogToLogcat(processGroups());
                 return 0;
             }
             case "disable" -> {
-                mProtoLogService.disableProtoLogToLogcat(processGroups());
+                mProtoLogConfigurationService.disableProtoLogToLogcat(processGroups());
                 return 0;
             }
             default -> {
@@ -159,7 +162,7 @@
     @NonNull
     private String[] processGroups() {
         if (getRemainingArgsCount() == 0) {
-            return mProtoLogService.getGroups();
+            return mProtoLogConfigurationService.getGroups();
         }
 
         final List<String> groups = new ArrayList<>();
diff --git a/core/java/com/android/internal/protolog/ProtoLogService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
similarity index 97%
rename from core/java/com/android/internal/protolog/ProtoLogService.java
rename to core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 2333a06..1765738 100644
--- a/core/java/com/android/internal/protolog/ProtoLogService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -70,9 +70,9 @@
  * <p>
  * This service is intended to run on the system server, such that it never gets frozen.
  */
-@SystemService(Context.PROTOLOG_SERVICE)
-public final class ProtoLogService extends IProtoLogService.Stub {
-    private static final String LOG_TAG = "ProtoLogService";
+@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
+public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub {
+    private static final String LOG_TAG = "ProtoLogConfigurationService";
 
     private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
             this::onTracingInstanceStart,
@@ -114,12 +114,12 @@
 
     private final ViewerConfigFileTracer mViewerConfigFileTracer;
 
-    public ProtoLogService() {
-        this(ProtoLogService::dumpTransitionTraceConfig);
+    public ProtoLogConfigurationService() {
+        this(ProtoLogConfigurationService::dumpTransitionTraceConfig);
     }
 
     @VisibleForTesting
-    public ProtoLogService(@NonNull ViewerConfigFileTracer tracer) {
+    public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) {
         // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
         // receive the lifecycle callbacks of the datasource and write the viewer configs if and
         // when required to the datasource.
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 6dc6585..ef6bece 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -17,6 +17,7 @@
 package com.android.internal.protolog;
 
 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT;
+import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT_LOG_FROM_LEVEL;
 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.ENABLE_ALL;
 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.GROUP_OVERRIDES;
 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.TRACING_MODE;
@@ -43,7 +44,6 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
 
 public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
         ProtoLogDataSource.TlsState,
@@ -190,73 +190,54 @@
         final Map<String, GroupConfig> groupConfigs = new HashMap<>();
 
         while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            if (configStream.getFieldNumber() == (int) TRACING_MODE) {
-                int tracingMode = configStream.readInt(TRACING_MODE);
-                switch (tracingMode) {
-                    case DEFAULT:
-                        break;
-                    case ENABLE_ALL:
-                        defaultLogFromLevel = LogLevel.DEBUG;
-                        break;
-                    default:
-                        throw new RuntimeException("Unhandled ProtoLog tracing mode type");
-                }
-            }
-            if (configStream.getFieldNumber() == (int) GROUP_OVERRIDES) {
-                final long group_overrides_token  = configStream.start(GROUP_OVERRIDES);
-
-                String tag = null;
-                LogLevel logFromLevel = defaultLogFromLevel;
-                boolean collectStackTrace = false;
-                while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    if (configStream.getFieldNumber() == (int) GROUP_NAME) {
-                        tag = configStream.readString(GROUP_NAME);
+            switch (configStream.getFieldNumber()) {
+                case (int) DEFAULT_LOG_FROM_LEVEL:
+                    int defaultLogFromLevelInt = configStream.readInt(DEFAULT_LOG_FROM_LEVEL);
+                    if (defaultLogFromLevelInt < defaultLogFromLevel.ordinal()) {
+                        defaultLogFromLevel =
+                                logLevelFromInt(defaultLogFromLevelInt);
                     }
-                    if (configStream.getFieldNumber() == (int) LOG_FROM) {
-                        final int logFromInt = configStream.readInt(LOG_FROM);
-                        switch (logFromInt) {
-                            case (ProtologCommon.PROTOLOG_LEVEL_DEBUG): {
-                                logFromLevel = LogLevel.DEBUG;
-                                break;
-                            }
-                            case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE): {
-                                logFromLevel = LogLevel.VERBOSE;
-                                break;
-                            }
-                            case (ProtologCommon.PROTOLOG_LEVEL_INFO): {
-                                logFromLevel = LogLevel.INFO;
-                                break;
-                            }
-                            case (ProtologCommon.PROTOLOG_LEVEL_WARN): {
-                                logFromLevel = LogLevel.WARN;
-                                break;
-                            }
-                            case (ProtologCommon.PROTOLOG_LEVEL_ERROR): {
-                                logFromLevel = LogLevel.ERROR;
-                                break;
-                            }
-                            case (ProtologCommon.PROTOLOG_LEVEL_WTF): {
-                                logFromLevel = LogLevel.WTF;
-                                break;
-                            }
-                            default: {
-                                throw new RuntimeException("Unhandled log level");
-                            }
+                    break;
+                case (int) TRACING_MODE:
+                    int tracingMode = configStream.readInt(TRACING_MODE);
+                    switch (tracingMode) {
+                        case DEFAULT:
+                            break;
+                        case ENABLE_ALL:
+                            defaultLogFromLevel = LogLevel.DEBUG;
+                            break;
+                        default:
+                            throw new RuntimeException("Unhandled ProtoLog tracing mode type");
+                    }
+                    break;
+                case (int) GROUP_OVERRIDES:
+                    final long group_overrides_token  = configStream.start(GROUP_OVERRIDES);
+
+                    String tag = null;
+                    LogLevel logFromLevel = defaultLogFromLevel;
+                    boolean collectStackTrace = false;
+                    while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                        if (configStream.getFieldNumber() == (int) GROUP_NAME) {
+                            tag = configStream.readString(GROUP_NAME);
+                        }
+                        if (configStream.getFieldNumber() == (int) LOG_FROM) {
+                            final int logFromInt = configStream.readInt(LOG_FROM);
+                            logFromLevel = logLevelFromInt(logFromInt);
+                        }
+                        if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
+                            collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
                         }
                     }
-                    if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
-                        collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
+
+                    if (tag == null) {
+                        throw new RuntimeException("Failed to decode proto config. "
+                                + "Got a group override without a group tag.");
                     }
-                }
 
-                if (tag == null) {
-                    throw new RuntimeException("Failed to decode proto config. "
-                            + "Got a group override without a group tag.");
-                }
+                    groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
 
-                groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
-
-                configStream.end(group_overrides_token);
+                    configStream.end(group_overrides_token);
+                    break;
             }
         }
 
@@ -265,6 +246,18 @@
         return new ProtoLogConfig(defaultLogFromLevel, groupConfigs);
     }
 
+    private LogLevel logLevelFromInt(int logFromInt) {
+        return switch (logFromInt) {
+            case (ProtologCommon.PROTOLOG_LEVEL_DEBUG) -> LogLevel.DEBUG;
+            case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE) -> LogLevel.VERBOSE;
+            case (ProtologCommon.PROTOLOG_LEVEL_INFO) -> LogLevel.INFO;
+            case (ProtologCommon.PROTOLOG_LEVEL_WARN) -> LogLevel.WARN;
+            case (ProtologCommon.PROTOLOG_LEVEL_ERROR) -> LogLevel.ERROR;
+            case (ProtologCommon.PROTOLOG_LEVEL_WTF) -> LogLevel.WTF;
+            default -> throw new RuntimeException("Unhandled log level");
+        };
+    }
+
     public static class Instance extends DataSourceInstance {
 
         public interface TracingInstanceStartCallback {
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
new file mode 100644
index 0000000..37d57ee
--- /dev/null
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "ProtologPerfTests"
+    }
+  ]
+}
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 76ce452..f8a1436 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.statusbar;
 
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -23,7 +24,18 @@
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
+/**
+ * Representation of an icon that should appear in the status bar.
+ *
+ * <p>This includes notifications, conversations, and icons displayed on the right side (e.g.
+ * Wifi, Vibration/Silence, Priority Modes, etc).
+ *
+ * <p>This class is {@link Parcelable} but the {@link #preloadedIcon} is not (and will be lost if
+ * the object is copied through parcelling). If {@link #preloadedIcon} is supplied, it must match
+ * the {@link #icon} resource/bitmap.
+ */
 public class StatusBarIcon implements Parcelable {
     public enum Type {
         // Notification: the sender avatar for important conversations
@@ -34,7 +46,9 @@
         // Notification: the small icon from the notification
         NotifSmallIcon,
         // The wi-fi, cellular or battery icon.
-        SystemIcon
+        SystemIcon,
+        // Some other icon, corresponding to a resource (possibly in a different package).
+        ResourceIcon
     }
 
     public UserHandle user;
@@ -46,6 +60,13 @@
     public CharSequence contentDescription;
     public Type type;
 
+    /**
+     * Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so
+     * will be lost if the object is sent to a different process. If you set it, make sure to
+     * <em>also</em> set {@link #icon} pointing to the corresponding resource.
+     */
+    @Nullable public Drawable preloadedIcon;
+
     public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
             CharSequence contentDescription, Type type) {
         if (icon.getType() == Icon.TYPE_RESOURCE
@@ -88,6 +109,7 @@
         StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
                 this.iconLevel, this.number, this.contentDescription, this.type);
         that.visible = this.visible;
+        that.preloadedIcon = this.preloadedIcon;
         return that;
     }
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 2068bd7..46b4695 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -466,10 +466,25 @@
 public:
     sp<JavaBBinder> get(JNIEnv* env, jobject obj)
     {
-        AutoMutex _l(mLock);
-        sp<JavaBBinder> b = mBinder.promote();
-        if (b == NULL) {
-            b = new JavaBBinder(env, obj);
+        sp<JavaBBinder> b;
+        {
+            AutoMutex _l(mLock);
+            // must take lock to promote because we set the same wp<>
+            // on another thread.
+            b = mBinder.promote();
+        }
+
+        if (b) return b;
+
+        // b/360067751: constructor may trigger GC, so call outside lock
+        b = new JavaBBinder(env, obj);
+
+        {
+            AutoMutex _l(mLock);
+            // if it was constructed on another thread in the meantime,
+            // return that. 'b' will just get destructed.
+            if (sp<JavaBBinder> b2 = mBinder.promote(); b2) return b2;
+
             if (mVintf) {
                 ::android::internal::Stability::markVintf(b.get());
             }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0f53164..17c89f8 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2089,9 +2089,11 @@
         jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
                 gJankDataClassInfo.clazz, nullptr);
         for (size_t i = 0; i < jankData.size(); i++) {
-            jobject jJankData = env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
-                                               jankData[i].frameVsyncId, jankData[i].jankType,
-                                               jankData[i].frameIntervalNs);
+            jobject jJankData =
+                    env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
+                                   jankData[i].frameVsyncId, jankData[i].jankType,
+                                   jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
+                                   jankData[i].actualAppFrameTimeNs);
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
             env->DeleteLocalRef(jJankData);
         }
@@ -2727,7 +2729,7 @@
     jclass jankDataClazz =
                 FindClassOrDie(env, "android/view/SurfaceControl$JankData");
     gJankDataClassInfo.clazz = MakeGlobalRefOrDie(env, jankDataClazz);
-    gJankDataClassInfo.ctor = GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JIJ)V");
+    gJankDataClassInfo.ctor = GetMethodIDOrDie(env, gJankDataClassInfo.clazz, "<init>", "(JIJJJ)V");
     jclass onJankDataListenerClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$OnJankDataListener");
     gJankDataListenerClassInfo.clazz = MakeGlobalRefOrDie(env, onJankDataListenerClazz);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2dd560c..17ff2eb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -843,6 +843,7 @@
     <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
     <protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
+    <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -3765,7 +3766,6 @@
         privileged app such as the Assistant app.
         <p>Protection level: internal|role
         <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-        @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT"
         android:protectionLevel="internal|role" />
@@ -3836,7 +3836,6 @@
     <!-- Allows an application to use audit logging API.
         @hide
         @SystemApi
-        @FlaggedApi("android.app.admin.flags.security_log_v2_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"
         android:protectionLevel="internal|role" />
@@ -4027,7 +4026,6 @@
         APIs protected by this permission on users different to the calling user.
         <p>Protection level: internal|role
         <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-        @FlaggedApi("android.app.admin.flags.esim_management_enabled")
     -->
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
         android:protectionLevel="internal|role" />
diff --git a/core/res/res/drawable/ic_zen_mode_type_special_dnd.xml b/core/res/res/drawable/ic_zen_mode_type_special_dnd.xml
new file mode 100644
index 0000000..fee9b0e
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_type_special_dnd.xml
@@ -0,0 +1,25 @@
+<!--
+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="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M280,520L680,520L680,440L280,440L280,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_priority_modes.xml b/core/res/res/drawable/ic_zen_priority_modes.xml
new file mode 100644
index 0000000..98de27b
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_priority_modes.xml
@@ -0,0 +1,25 @@
+<!--
+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="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?android:attr/colorControlNormal">
+  <path
+      android:pathData="M160,480v-80h320v80L160,480ZM480,880q-80,0 -153.5,-29.5T196,764l56,-56q47,44 106,68t122,24q133,0 226.5,-93.5T800,480q0,-133 -93.5,-226.5T480,160v-80q83,0 155.5,31.5t127,86q54.5,54.5 86,127T880,480q0,82 -31.5,155t-86,127.5q-54.5,54.5 -127,86T480,880Z"
+      android:fillColor="@android:color/white"/>
+</vector>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index bf5884b..381111c 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Terug"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Wissel invoermetode"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Maak invoermetodekieser oop"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Instellings"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Bergingspasie word min"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sommige stelselfunksies werk moontlik nie"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nie genoeg berging vir die stelsel nie. Maak seker jy het 250 MB spasie beskikbaar en herbegin."</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index d97c903..aa7dcec 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ተመለስ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"የግቤት ስልትን ቀይር"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"የግቤት ስልት መራጭን ክፈት"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ቅንብሮች"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"የማከማቻ ቦታ እያለቀ ነው"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነፃ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c798341..4f03ff3 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1199,8 +1199,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"رجوع"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"تبديل أسلوب الإدخال"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"فتح أداة اختيار أسلوب الإدخال"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"الإعدادات"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"مساحة التخزين منخفضة"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"قد لا تعمل بعض وظائف النظام"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ليست هناك مساحة تخزين كافية للنظام. تأكد من أنه لديك مساحة خالية تبلغ ٢٥٠ ميغابايت وأعد التشغيل."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index af97d43..8c9cda9 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"উভতি যাওক"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ইনপুটৰ পদ্ধতি সলনি কৰক"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ইনপুট পদ্ধতি বাছনিকর্তা খোলক"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ছেটিং"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ষ্ট’ৰেজৰ খালী ঠাই শেষ হৈ আছে"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ছিষ্টেমৰ কিছুমান কাৰ্যকলাপে কাম নকৰিবও পাৰে"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ছিষ্টেমৰ বাবে পৰ্যাপ্ত খালী ঠাই নাই। আপোনাৰ ২৫০এমবি খালী ঠাই থকাটো নিশ্চিত কৰক আৰু ৰিষ্টাৰ্ট কৰক।"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 72daeaf..afbe715 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Geriyə"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Daxiletmə metodunu dəyişdirin"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Daxiletmə metodu seçicisini açın"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ayarlar"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Yaddaş yeri bitir"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bəzi sistem funksiyaları işləməyə bilər"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistem üçün yetərincə yaddaş ehtiyatı yoxdur. 250 MB yaddaş ehtiyatının olmasına əmin olun və yenidən başladın."</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 917df57..a20775d 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Nazad"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Promenite metod unosa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otvori birač metoda unosa"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Podešavanja"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Memorijski prostor je na izmaku"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke sistemske funkcije možda ne funkcionišu"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno memorijskog prostora za sistem. Uverite se da imate 250 MB slobodnog prostora i ponovo pokrenite."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 86e81d1..871f8d4 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Пераключэнне рэжыму ўводу"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Выбраць спосаб уводу"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Налады"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Месца для захавання на зыходзе"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некаторыя сістэмныя функцыі могуць не працаваць"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Не хапае сховішча для сістэмы. Пераканайцеся, што ў вас ёсць 250 МБ свабоднага месца, і перазапусціце."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9f86946..c22325c 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Превключване на метода на въвеждане"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Отваряне на инструмента за избор на метод на въвеждане"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Настройки"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Мястото в хранилището е на изчерпване"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Възможно е някои функции на системата да не работят"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"За системата няма достатъчно място в хранилището. Уверете се, че имате свободни 250 МБ, и рестартирайте."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 9bcefa7..776714c 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ফিরে যান"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ইনপুট পদ্ধতি পাল্টান"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ইনপুট পদ্ধতির পিকার খুলুন"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"সেটিংস"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"স্টোরেজ পূর্ণ হতে চলেছে"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"কিছু কিছু সিস্টেম ক্রিয়াকলাপ কাজ নাও করতে পারে"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"সিস্টেমের জন্য যথেষ্ট স্টোরেজ নেই৷ আপনার কাছে ২৫০এমবি ফাঁকা স্থান রয়েছে কিনা সে বিষয়ে নিশ্চিত হন এবং সিস্টেম চালু করুন৷"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 017e8b4..9306cfd 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Nazad"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Promjena načina unosa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otvaranje birača načina unosa"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Postavke"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ponestaje prostora za pohranu"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke funkcije sistema možda neće raditi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno prostora za sistem. Obezbijedite 250MB slobodnog prostora i ponovo pokrenite uređaj."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index e3b37341..80420dd 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Enrere"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Canvia el mètode d\'introducció de text"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Obre el selector de mètode d\'introducció"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Configuració"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"L\'espai d\'emmagatzematge s\'està esgotant"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"És possible que algunes funcions del sistema no funcionin"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hi ha prou espai d\'emmagatzematge per al sistema. Comprova que tinguis 250 MB d\'espai lliure i reinicia."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 5dde261..bc5cdfe 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Zpět"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Přepnout metodu zadávání"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otevřít výběr metody zadávání"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Nastavení"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"V úložišti je málo místa"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Některé systémové funkce nemusí fungovat"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Pro systém není dostatek místa v úložišti. Uvolněte alespoň 250 MB místa a restartujte zařízení."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 00fc1eb..6cbe673 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Tilbage"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Skift indtastningsmetode"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Åbn indtastningsmetodevælgeren"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Indstillinger"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Der er snart ikke mere lagerplads"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Nogle systemfunktioner virker måske ikke"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Der er ikke nok ledig lagerplads til systemet. Sørg for, at du har 250 MB ledig plads, og genstart."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index bc35788..dece83f 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Zurück"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Eingabemethode wechseln"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Auswahl für die Eingabemethode öffnen"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Einstellungen"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Der Speicherplatz wird knapp"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Einige Systemfunktionen funktionieren eventuell nicht."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Der Speicherplatz reicht nicht für das System aus. Stelle sicher, dass 250 MB freier Speicherplatz vorhanden sind, und starte das Gerät dann neu."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index b9f3d54..cc179a4 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1029,7 +1029,7 @@
     <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"Έχετε πληκτρολογήσει τον κωδικό πρόσβασης εσφαλμένα <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. \n\nΠροσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%2$d</xliff:g> δευτερόλεπτα."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Έχετε πληκτρολογήσει τον αριθμό σας PIN εσφαλμένα <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. \n\nΠροσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%2$d</xliff:g> δευτερόλεπτα."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το tablet σας με τη χρήση της σύνδεσής σας Google.\n\n Προσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%3$d</xliff:g> δευτερόλεπτα."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Σχεδιάσατε εσφαλμένα το μοτίβο ξεκλειδώματος <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από ακόμα <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες, θα σας ζητηθεί να ξεκλειδώσετε τη συσκευή σας Android TV χρησιμοποιώντας τα στοιχεία σύνδεσής σας στο Google.\n\nΠροσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%3$d</xliff:g> δευτερόλεπτα."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Σχεδιάσατε εσφαλμένα το μοτίβο ξεκλειδώματος <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από ακόμα <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες, θα σας ζητηθεί να ξεκλειδώσετε τη συσκευή σας Android TV χρησιμοποιώντας τα στοιχεία σύνδεσής σας στην Google.\n\nΠροσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%3$d</xliff:g> δευτερόλεπτα."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το τηλέφωνό σας με τη χρήση της σύνδεσής σας Google.\n\n Προσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%3$d</xliff:g> δευτερόλεπτα."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Προσπαθήσατε να ξεκλειδώσετε εσφαλμένα το tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%2$d</xliff:g> προσπάθειες, το tablet θα επαναφερθεί στις εργοστασιακές ρυθμίσεις και όλα τα δεδομένα χρήστη θα χαθούν."</string>
     <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Δοκιμάσατε να ξεκλειδώσετε τη συσκευή Android TV <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές χωρίς επιτυχία. Μετά από ακόμα <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες, θα γίνει επαναφορά των προεπιλεγμένων εργοστασιακών ρυθμίσεων στη συσκευή σας Android TV και όλα τα δεδομένα χρήστη θα χαθούν."</string>
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Πίσω"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Εναλλαγή μεθόδου εισαγωγής"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Άνοιγμα εργαλείου επιλογής μεθόδου εισαγωγής"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ρυθμίσεις"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ο αποθηκευτικός χώρος εξαντλείται"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Ορισμένες λειτουργίες συστήματος ενδέχεται να μην λειτουργούν"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Δεν υπάρχει αρκετός αποθηκευτικός χώρος για το σύστημα. Βεβαιωθείτε ότι διαθέτετε 250 MB ελεύθερου χώρου και κάντε επανεκκίνηση."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 6f265df..caa52c4 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Back"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Open input method picker"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Settings"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 6a4bc1a..3850b00 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Back"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Open input method picker"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Settings"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 0bf754c..bf5c61c 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Back"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Open input method picker"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Settings"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 04f5d5c..e829fa3 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Back"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Open input method picker"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Settings"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 74d2fbe..0b9be3b 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎Back‎‏‎‎‏‎"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎Switch input method‎‏‎‎‏‎"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‎Open input method picker‎‏‎‎‏‎"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎Settings‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎Storage space running out‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎Some system functions may not work‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎Not enough storage for the system. Make sure you have 250MB of free space and restart.‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index b94201a..3814944 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atrás"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambiar método de entrada"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir selector de método de entrada"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Configuración"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio de almacenamiento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Es posible que algunas funciones del sistema no estén disponibles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hay espacio suficiente para el sistema. Asegúrate de que haya 250 MB libres y reinicia el dispositivo."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 77f10f5..215cf39 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atrás"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambiar método de introducción de texto"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir selector de método de introducción"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ajustes"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Es posible que algunas funciones del sistema no funcionen."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hay espacio suficiente para el sistema. Comprueba que haya 250 MB libres y reinicia el dispositivo."</string>
@@ -2393,13 +2392,13 @@
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
     <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Tu dispositivo está demasiado caliente y no puede proyectar a la pantalla hasta que se enfríe"</string>
-    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
-    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
+    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
+    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Pantalla dual está activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
     <string name="concurrent_display_notification_thermal_title" msgid="5921609404644739229">"El dispositivo está demasiado caliente"</string>
-    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"Dual Screen no está disponible porque el teléfono se está calentando demasiado"</string>
-    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"Dual Screen no está disponible"</string>
-    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"Dual Screen no está disponible porque la función Ahorro de batería está activada. Puedes desactivarla en Ajustes."</string>
+    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"Pantalla dual no está disponible porque el teléfono se está calentando demasiado"</string>
+    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"Pantalla dual no está disponible"</string>
+    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"Pantalla dual no está disponible porque la función Ahorro de batería está activada. Puedes desactivarla en Ajustes."</string>
     <string name="device_state_notification_settings_button" msgid="691937505741872749">"Ir a Ajustes"</string>
     <string name="device_state_notification_turn_off_button" msgid="6327161707661689232">"Desactivar"</string>
     <string name="keyboard_layout_notification_selected_title" msgid="1202560174252421219">"<xliff:g id="DEVICE_NAME">%s</xliff:g> configurado"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 1db2c5d..4694efa 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Tagasi"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Sisestusmeetodi vahetamine"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Sisestusmeetodi valija avamine"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Seaded"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Talletusruum saab täis"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Mõned süsteemifunktsioonid ei pruugi töötada"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Süsteemis pole piisavalt talletusruumi. Veenduge, et seadmes oleks 250 MB vaba ruumi, ja käivitage seade uuesti."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 6f92745..2cae18a 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atzera"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Aldatu idazketa-metodoa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Ireki idazketa-metodoaren hautatzailea"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ezarpenak"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Memoria betetzen ari da"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sistemaren funtzio batzuek ez dute agian funtzionatuko"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sisteman ez dago behar adina memoria. Ziurtatu gutxienez 250 MB erabilgarri dituzula eta, ondoren, berrabiarazi gailua."</string>
@@ -1335,7 +1334,7 @@
     <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarma-soinuak"</string>
     <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Jakinarazpen-soinuak"</string>
     <string name="ringtone_unknown" msgid="5059495249862816475">"Ezezaguna"</string>
-    <string name="wifi_available_sign_in" msgid="381054692557675237">"Hasi saioa Wi-Fi sarean"</string>
+    <string name="wifi_available_sign_in" msgid="381054692557675237">"Hasi saioa wifi-sarean"</string>
     <string name="network_available_sign_in" msgid="1520342291829283114">"Hasi saioa sarean"</string>
     <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
     <skip />
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 728d87d..9a75d3a 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"برگشت"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"تغییر روش ورودی"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"باز کردن انتخابگر روش ورودی"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"تنظیمات"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"فضای ذخیره‌سازی درحال پر شدن است"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"برخی از عملکردهای سیستم ممکن است کار نکنند"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"فضای ذخیره‌سازی سیستم کافی نیست. اطمینان حاصل کنید که دارای ۲۵۰ مگابایت فضای خالی هستید و سیستم را راه‌اندازی مجدد کنید."</string>
@@ -2392,13 +2391,13 @@
     <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"قرینه‌سازی روی نمایشگر ممکن نبود"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
     <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"دستگاه بسیار گرم است و تا زمانی‌که خنک نشود نمی‌تواند محتوا را روی نمایشگر قرینه‌سازی کند."</string>
-    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"‫Dual screen"</string>
-    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‏‫Dual Screen روشن است"</string>
+    <string name="concurrent_display_notification_name" msgid="1526911253558311131">"صفحه‌نمایش دوگانه"</string>
+    <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‫«صفحه‌نمایش دوگانه» روشن است"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> از هر دو نمایشگر برای نمایش محتوا استفاده می‌کند"</string>
     <string name="concurrent_display_notification_thermal_title" msgid="5921609404644739229">"دستگاه بیش‌ازحد گرم شده است"</string>
-    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"‏‫Dual Screen دردسترس نیست زیرا تلفن بیش‌ازحد گرم شده است"</string>
-    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"‏Dual Screen دردسترس نیست"</string>
-    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"‏Dual Screen دردسترس نیست چون «بهینه‌سازی باتری» روشن است. می‌توانید این ویژگی را در «تنظیمات» خاموش کنید."</string>
+    <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"‫«صفحه‌نمایش دوگانه» دردسترس نیست زیرا تلفن بیش‌ازحد گرم شده است"</string>
+    <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"«صفحه‌نمایش دوگانه» دردسترس نیست"</string>
+    <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"«صفحه‌نمایش دوگانه» دردسترس نیست چون «بهینه‌سازی باتری» روشن است. می‌توانید این ویژگی را در «تنظیمات» خاموش کنید."</string>
     <string name="device_state_notification_settings_button" msgid="691937505741872749">"رفتن به تنظیمات"</string>
     <string name="device_state_notification_turn_off_button" msgid="6327161707661689232">"خاموش کردن"</string>
     <string name="keyboard_layout_notification_selected_title" msgid="1202560174252421219">"‫<xliff:g id="DEVICE_NAME">%s</xliff:g> پیکربندی شد"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 776b2db..1b2ddb0 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Takaisin"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Vaihda syöttötapaa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Avaa syöttötavan valinta"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Asetukset"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Tallennustila loppumassa"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Kaikki järjestelmätoiminnot eivät välttämättä toimi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tallennustila ei riitä. Varmista, että vapaata tilaa on 250 Mt, ja käynnistä uudelleen."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 3256151..d3ebe5a 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Retour"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Changer de méthode d\'entrée"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Ouvrir le sélecteur de méthode d\'entrée"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Paramètres"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Espace de stockage bientôt saturé"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index a6aee4c..d617143 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Retour"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Changer le mode de saisie"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Ouvrir l\'outil de sélection du mode de saisie"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Paramètres"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Espace de stockage bientôt saturé"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 85d3eed..9366f4e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atrás"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambia o método de introdución"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir o selector do método de introdución de texto"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Configuración"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Estase esgotando o espazo de almacenamento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"É posible que algunhas funcións do sistema non funcionen"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Non hai almacenamento suficiente para o sistema. Asegúrate de ter un espazo libre de 250 MB e reinicia o dispositivo."</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 6c028ed..6312704 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -90,7 +90,7 @@
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"કટોકટી કૉલબૅક મોડ"</string>
     <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"મોબાઇલ ડેટાની સ્થિતિ"</string>
     <string name="notification_channel_sms" msgid="1243384981025535724">"SMS મેસેજ"</string>
-    <string name="notification_channel_voice_mail" msgid="8457433203106654172">"વૉઇસમેઇલ સંદેશા"</string>
+    <string name="notification_channel_voice_mail" msgid="8457433203106654172">"વૉઇસમેઇલ મેસેજ"</string>
     <string name="notification_channel_wfc" msgid="9048240466765169038">"વાઇ-ફાઇ કૉલિંગ"</string>
     <string name="notification_channel_sim" msgid="5098802350325677490">"સિમનું સ્ટેટસ"</string>
     <string name="notification_channel_sim_high_prio" msgid="642361929452850928">"સિમ કાર્ડનું ઉચ્ચ પ્રાધાન્યતાનું સ્ટેટસ"</string>
@@ -122,7 +122,7 @@
     <string name="roamingTextSearching" msgid="5323235489657753486">"સેવા શોધી રહ્યું છે"</string>
     <string name="wfcRegErrorTitle" msgid="3193072971584858020">"વાઇ-ફાઇ કૉલિંગ સેટ કરી શકાયું નથી"</string>
   <string-array name="wfcOperatorErrorAlertMessages">
-    <item msgid="468830943567116703">"વાઇ-ફાઇ પરથી કૉલ કરવા અને સંદેશા મોકલવા માટે પહેલાં તમારા કૅરિઅરને આ સેવા સેટ કરવા માટે કહો. પછી સેટિંગમાંથી વાઇ-ફાઇ કૉલિંગ ફરીથી ચાલુ કરો. (ભૂલ કોડ: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
+    <item msgid="468830943567116703">"વાઇ-ફાઇ પરથી કૉલ કરવા અને મેસેજ મોકલવા માટે પહેલાં તમારા મોબાઇલ ઑપરેટરને આ સેવા સેટ કરવા માટે કહો. પછી સેટિંગમાંથી વાઇ-ફાઇ કૉલિંગ ફરીથી ચાલુ કરો. (ભૂલ કોડ: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
   </string-array>
   <string-array name="wfcOperatorErrorNotificationMessages">
     <item msgid="4795145070505729156">"તમારા કૅરિઅરમાં વાઇ-ફાઇ કૉલિંગ રજિસ્ટર કરવામાં સમસ્યા આવી: <xliff:g id="CODE">%1$s</xliff:g>"</item>
@@ -292,7 +292,7 @@
     <string name="notification_channel_car_mode" msgid="2123919247040988436">"કાર મોડ"</string>
     <string name="notification_channel_account" msgid="6436294521740148173">"એકાઉન્ટ સ્થિતિ"</string>
     <string name="notification_channel_developer" msgid="1691059964407549150">"વિકાસકર્તા માટેના સંદેશા"</string>
-    <string name="notification_channel_developer_important" msgid="7197281908918789589">"ડેવલપર માટેના મહત્ત્વપૂર્ણ સંદેશા"</string>
+    <string name="notification_channel_developer_important" msgid="7197281908918789589">"ડેવલપર માટેના મહત્ત્વપૂર્ણ મેસેજ"</string>
     <string name="notification_channel_updates" msgid="7907863984825495278">"અપડેટ્સ"</string>
     <string name="notification_channel_network_status" msgid="2127687368725272809">"નેટવર્ક સ્થિતિ"</string>
     <string name="notification_channel_network_alerts" msgid="6312366315654526528">"નેટવર્ક ચેતવણીઓ"</string>
@@ -324,7 +324,7 @@
     <string name="permgrouplab_calendar" msgid="6426860926123033230">"કૅલેન્ડર"</string>
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"તમારા કેલેન્ડરને ઍક્સેસ કરવાની"</string>
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
-    <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS સંદેશા મોકલવાની અને જોવાની"</string>
+    <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS મેસેજ મોકલવાની અને જોવાની"</string>
     <string name="permgrouplab_storage" msgid="17339216290379241">"ફાઇલો"</string>
     <string name="permgroupdesc_storage" msgid="5378659041354582769">"તમારા ડિવાઇસ પરની ફાઇલો ઍક્સેસ કરો"</string>
     <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"મ્યુઝિક અને ઑડિયો"</string>
@@ -379,27 +379,27 @@
     <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"એપ્લિકેશનને આઉટગોઇંગ કૉલ દરમિયાન કૉલને એક અલગ નંબર પર રીડાયરેક્ટ કરવા અથવા કૉલને સંપૂર્ણપણે છોડી દેવાનાં વિકલ્પ સાથે ડાયલ થઈ રહેલા નંબરને જોવાની મંજૂરી આપે છે."</string>
     <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ફોન કૉલને જવાબ આપો"</string>
     <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ઍપ્લિકેશનને ઇનકમિંગ ફોન કૉલને જવાબ આપવાની મંજૂરી આપે છે."</string>
-    <string name="permlab_receiveSms" msgid="505961632050451881">"ટેક્સ્ટ સંદેશા (SMS) પ્રાપ્ત કરો"</string>
-    <string name="permdesc_receiveSms" msgid="1797345626687832285">"ઍપ્લિકેશનને SMS સંદેશા પ્રાપ્ત કરવાની અને તેના પર પ્રક્રિયા કરવાની મંજૂરી આપે છે. આનો અર્થ એ કે ઍપ્લિકેશન તમને દર્શાવ્યા વિના તમારા ઉપકરણ પર મોકલેલ સંદેશાઓનું નિરીક્ષણ કરી શકે છે અથવા કાઢી નાખી શકે છે."</string>
-    <string name="permlab_receiveMms" msgid="4000650116674380275">"ટેક્સ્ટ સંદેશા (MMS) પ્રાપ્ત કરો"</string>
-    <string name="permdesc_receiveMms" msgid="958102423732219710">"ઍપ્લિકેશનને MMS સંદેશા પ્રાપ્ત કરવાની અને તેના પર પ્રક્રિયા કરવાની મંજૂરી આપે છે. આનો અર્થ એ કે ઍપ્લિકેશન તમને દર્શાવ્યા વિના તમારા ઉપકરણ પર મોકલેલ સંદેશાઓનું નિરીક્ષણ કરી શકે છે અથવા કાઢી નાખી શકે છે."</string>
-    <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"સેલ બ્રોડકાસ્ટ સંદેશા ફૉરવર્ડ કરો"</string>
-    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"સેલ બ્રોડકાસ્ટ સંદેશા પ્રાપ્ત થાય કે તરત ફૉરવર્ડ કરવા માટે સેલ બ્રોડકાસ્ટ મૉડ્યૂલ સાથે પ્રતિબદ્ધ થવા બાબતે ઍપને મંજૂરી આપે છે. તમને કટોકટીની પરિસ્થિતિની ચેતવણી આપવા માટે સેલ બ્રોડકાસ્ટ અલર્ટ અમુક સ્થાનોમાં ડિલિવર કરવામાં આવે છે. કટોકટી અંગેનો સેલ બ્રોડકાસ્ટ પ્રાપ્ત થાય, ત્યારે દુર્ભાવનાપૂર્ણ ઍપ તમારા ડિવાઇસના કાર્યપ્રદર્શન અથવા ઑપરેશનમાં વિક્ષેપ પાડે તેમ બની શકે છે."</string>
+    <string name="permlab_receiveSms" msgid="505961632050451881">"ટેક્સ્ટ મેસેજ (SMS) મેળવો"</string>
+    <string name="permdesc_receiveSms" msgid="1797345626687832285">"ઍપને SMS મેસેજ મેળવવાની અને તેના પર પ્રક્રિયા કરવાની મંજૂરી આપે છે. આનો અર્થ એ કે ઍપ તમને દર્શાવ્યા વિના તમારા ડિવાઇસ પર મોકલેલા મેસેજનું નિરીક્ષણ કરી શકે છે અથવા કાઢી નાખી શકે છે."</string>
+    <string name="permlab_receiveMms" msgid="4000650116674380275">"ટેક્સ્ટ મેસેજ (MMS) મેળવો"</string>
+    <string name="permdesc_receiveMms" msgid="958102423732219710">"ઍપને MMS મેસેજ પ્રાપ્ત કરવાની અને તેના પર પ્રક્રિયા કરવાની મંજૂરી આપે છે. આનો અર્થ એ કે ઍપ તમને દર્શાવ્યા વિના તમારા ડિવાઇસ પર મોકલેલા મેસેજને મૉનિટર કરી શકે છે અથવા ડિલીટ કરી શકે છે."</string>
+    <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"સેલ બ્રોડકાસ્ટ મેસેજ ફૉરવર્ડ કરો"</string>
+    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"સેલ બ્રોડકાસ્ટ મેસેજ પ્રાપ્ત થાય કે તરત ફૉરવર્ડ કરવા માટે સેલ બ્રોડકાસ્ટ મૉડ્યૂલ સાથે પ્રતિબદ્ધ થવા બાબતે ઍપને મંજૂરી આપે છે. તમને કટોકટીની પરિસ્થિતિની ચેતવણી આપવા માટે સેલ બ્રોડકાસ્ટ અલર્ટ અમુક સ્થાનોમાં ડિલિવર કરવામાં આવે છે. કટોકટી અંગેનો સેલ બ્રોડકાસ્ટ પ્રાપ્ત થાય, ત્યારે દુર્ભાવનાપૂર્ણ ઍપ તમારા ડિવાઇસના કાર્યપ્રદર્શન અથવા ઑપરેશનમાં વિક્ષેપ પાડે તેમ બની શકે છે."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ચાલી રહેલા કૉલ મેનેજ કરો"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ઍપને તમારા ડિવાઇસ પર ચાલુ કૉલ વિશેની વિગતો જોવાની અને આ કૉલને નિયંત્રિત કરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_accessLastKnownCellId" msgid="7638226620825665130">"છેલ્લી જ્ઞાત સેલ ઓળખને ઍક્સેસ કરો."</string>
     <string name="permdesc_accessLastKnownCellId" msgid="6664621339249308857">"ઍપને ટેલિફોન દ્વારા પ્રદાન કરવામાં આવેલી છેલ્લી જ્ઞાત સેલ ઓળખને ઍક્સેસ કરવાની મંજૂરી આપે છે."</string>
-    <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"સેલ બ્રોડકાસ્ટ સંદેશા વાંચો"</string>
+    <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"સેલ બ્રોડકાસ્ટ મેસેજ વાંચો"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ઍપ તમારા ડિવાઇસ દ્વારા પ્રાપ્ત થયેલ સેલ બ્રોડકાસ્ટ સંદેશાને વાંચવાની મંજૂરી આપે છે. સેલ બ્રોડકાસ્ટ ચેતવણીઓ તમને ઇમર્જન્સીની સ્થિતિઓ અંગે ચેતવવા માટે કેટલાક સ્થાનોમાં વિતરિત થાય છે. જ્યારે ઇમર્જન્સીનો સેલ બ્રોડકાસ્ટ પ્રાપ્ત થાય ત્યારે દુર્ભાવનાપૂર્ણ ઍપ તમારા ડિવાઇસના કાર્યપ્રદર્શન અથવા ઓપરેશનમાં હસ્તક્ષેપ કરી શકે છે."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"સબ્સ્ક્રાઇબ કરેલ ફીડ્સ વાંચો"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"એપ્લિકેશનને હાલમાં સમન્વયિત ફીડ્સ વિશે વિગતો મેળવવાની મંજૂરી આપે છે."</string>
-    <string name="permlab_sendSms" msgid="7757368721742014252">"SMS સંદેશા મોકલો અને જુઓ"</string>
-    <string name="permdesc_sendSms" msgid="6757089798435130769">"એપ્લિકેશનને SMS સંદેશા મોકલવાની મંજૂરી આપે છે. આના પરિણામે અનપેક્ષિત શુલ્ક લાગી શકે છે. દુર્ભાવનાપૂર્ણ ઍપ્લિકેશનો તમારી પુષ્ટિ વિના સંદેશા મોકલીને તમારા નાણા ખર્ચાવી શકે છે."</string>
-    <string name="permlab_readSms" msgid="5164176626258800297">"તમારા ટેક્સ્ટ સંદેશા (SMS અથવા MMS) વાંચો"</string>
-    <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"આ ઍપ્લિકેશન, તમારા ટેબ્લેટ પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) સંદેશા વાંચી શકે છે."</string>
-    <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"આ ઍપ, તમારા Android TV ડિવાઇસ પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) સંદેશા વાંચી શકે છે."</string>
-    <string name="permdesc_readSms" product="default" msgid="774753371111699782">"આ ઍપ્લિકેશન, તમારા ફોન પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) સંદેશા વાંચી શકે છે."</string>
-    <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ટેક્સ્ટ સંદેશા (WAP) પ્રાપ્ત કરો"</string>
+    <string name="permlab_sendSms" msgid="7757368721742014252">"SMS મેસેજ મોકલો અને જુઓ"</string>
+    <string name="permdesc_sendSms" msgid="6757089798435130769">"ઍપને SMS મેસેજ મોકલવાની મંજૂરી આપે છે. આના પરિણામે અનપેક્ષિત શુલ્ક લાગી શકે છે. દુર્ભાવનાપૂર્ણ ઍપ તમારા કન્ફર્મેશન વિના મેસેજ મોકલીને તમારા નાણા ખર્ચાવી શકે છે."</string>
+    <string name="permlab_readSms" msgid="5164176626258800297">"તમારા ટેક્સ્ટ મેસેજ (SMS અથવા MMS) વાંચો"</string>
+    <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"આ ઍપ, તમારા ટેબ્લેટ પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) મેસેજ વાંચી શકે છે."</string>
+    <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"આ ઍપ, તમારા Android TV ડિવાઇસ પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) મેસેજ વાંચી શકે છે."</string>
+    <string name="permdesc_readSms" product="default" msgid="774753371111699782">"આ ઍપ, તમારા ફોન પર સંગ્રહિત તમામ SMS (ટેક્સ્ટ) મેસેજ વાંચી શકે છે."</string>
+    <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ટેક્સ્ટ મેસેજ  (WAP) મેળવો"</string>
     <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"એપ્લિકેશનને WAP સંદેશા પ્રાપ્ત કરવાની અને તેના પર પ્રક્રિયા કરવાની મંજૂરી આપે છે. આ પરવાનગીમાં તમને દર્શાવ્યા વિના તમને મોકલેલ સંદેશાઓનું નિરીક્ષણ કરવાની અને કાઢી નાખવાની ક્ષમતાનો સમાવેશ થાય છે."</string>
     <string name="permlab_getTasks" msgid="7460048811831750262">"ચાલુ ઍપ્લિકેશનો પુનઃપ્રાપ્ત કરો"</string>
     <string name="permdesc_getTasks" msgid="7388138607018233726">"એપ્લિકેશનને વર્તમાનમાં અને તાજેતરમાં ચાલી રહેલ Tasks વિશેની વિગતવાર માહિતી પુનઃપ્રાપ્ત કરવાની મંજૂરી આપે છે. આ એપ્લિકેશનને ઉપકરણ પર કઈ એપ્લિકેશન્સનો ઉપયોગ થાય છે તેના વિશેની માહિતી શોધવાની મંજૂરી આપી શકે છે."</string>
@@ -492,9 +492,9 @@
     <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"આ ઍપ, તમારા Android TV ડિવાઇસ પર સંગ્રહિત બધા કૅલેન્ડર ઇવેન્ટને વાંચી શકે છે અને તમારા કૅલેન્ડર ડેટાને શેર કરી અથવા સાચવી શકે છે."</string>
     <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"આ ઍપ્લિકેશન, તમારા ફોન પર સંગ્રહિત તમામ કૅલેન્ડર ઇવેન્ટ્સને વાંચી શકે છે અને તમારા કૅલેન્ડર ડેટાને શેર કરી અથવા સાચવી શકે છે."</string>
     <string name="permlab_writeCalendar" msgid="6422137308329578076">"કૅલેન્ડર  ઇવેન્ટ્સ ઉમેરો અથવા સંશોધિત કરો અને માલિકની જાણ બહાર અતિથિઓને ઇમેઇલ મોકલો"</string>
-    <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"આ ઍપ્લિકેશન, તમારા ટેબ્લેટ પર કૅલેન્ડર ઇવેન્ટ્સ ઉમેરી, દૂર કરી અથવા બદલી શકે છે. આ ઍપ્લિકેશન, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં સંદેશા મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના ઇવેન્ટ્સ બદલી શકે છે."</string>
-    <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"આ ઍપ, તમારા Android TV ડિવાઇસ પર કૅલેન્ડર ઇવેન્ટ ઉમેરી, કાઢી નાખી અથવા બદલી શકે છે. આ ઍપ, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં સંદેશા મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના ઇવેન્ટ બદલી શકે છે."</string>
-    <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"આ ઍપ્લિકેશન, તમારા ફોન પર કૅલેન્ડર ઇવેન્ટ્સ ઉમેરી, દૂર કરી અથવા બદલી શકે છે. આ ઍપ્લિકેશન, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં સંદેશા મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના ઇવેન્ટ્સ બદલી શકે છે."</string>
+    <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"આ ઍપ, તમારા ટેબ્લેટ પર કૅલેન્ડર ઇવેન્ટ ઉમેરી, દૂર કરી અથવા બદલી શકે છે. આ ઍપ, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં મેસેજ મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના કૅલેન્ડર બદલી શકે છે."</string>
+    <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"આ ઍપ, તમારા Android TV ડિવાઇસ પર કૅલેન્ડર ઇવેન્ટ ઉમેરી, કાઢી નાખી અથવા બદલી શકે છે. આ ઍપ, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં મેસેજ મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના ઇવેન્ટ બદલી શકે છે."</string>
+    <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"આ ઍપ, તમારા ફોન પર કૅલેન્ડર ઇવેન્ટ ઉમેરી, દૂર કરી અથવા બદલી શકે છે. આ ઍપ, કૅલેન્ડર માલિકો તરફથી આવતાં હોય તેવા લાગતાં મેસેજ મોકલી શકે છે અથવા તેમના માલિકોને સૂચિત કર્યા વિના ઇવેન્ટ બદલી શકે છે."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"વધારાના સ્થાન પ્રદાતા આદેશોને ઍક્સેસ કરો"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"એપ્લિકેશનને વધારાના સ્થાન પ્રદાતા આદેશોને ઍક્સેસ કરવાની મંજૂરી આપે છે. આ એપ્લિકેશનને GPS અથવા અન્ય સ્થાન સ્રોતોના ઓપરેશનમાં દખલ કરવાની મંજૂરી આપી શકે છે."</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"ફૉરગ્રાઉન્ડમાં ફક્ત ચોક્કસ સ્થાન ઍક્સેસ કરો"</string>
@@ -1101,7 +1101,7 @@
     <string name="permlab_setAlarm" msgid="1158001610254173567">"એલાર્મ સેટ કરો"</string>
     <string name="permdesc_setAlarm" msgid="2185033720060109640">"એપ્લિકેશનને ઇન્સ્ટોલ કરેલ અલાર્મ ઘડિયાળ એપ્લિકેશનમાં અલાર્મ સેટ કરવાની મંજૂરી આપે છે. કેટલીક અલાર્મ ઘડિયાળ ઍપ્લિકેશનો, આ સુવિધા લાગુ કરી શકતી નથી."</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"વૉઇસમેઇલ ઉમેરો"</string>
-    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"એપ્લિકેશનને તમારા વૉઇસમેઇલ ઇનબોક્સ પર સંદેશા ઉમેરવાની મંજૂરી આપે છે."</string>
+    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"એપને તમારા વૉઇસમેઇલ ઇનબોક્સ પર મેસેજ ઉમેરવાની મંજૂરી આપે છે."</string>
     <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> દ્વારા તમારા ક્લિપબોર્ડ પરથી પેસ્ટ કરવામાં આવ્યું"</string>
     <string name="more_item_label" msgid="7419249600215749115">"વધુ"</string>
     <string name="prepend_shortcut_label" msgid="1743716737502867951">"મેનૂ+"</string>
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"પાછળ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ઇનપુટ પદ્ધતિ સ્વિચ કરો"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ઇનપુટ પદ્ધતિ પિકર ખોલો"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"સેટિંગ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"સ્ટોરેજ સ્થાન સમાપ્ત થયું"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"કેટલાક સિસ્ટમ Tasks કામ કરી શકશે નહીં"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"સિસ્ટમ માટે પર્યાપ્ત સ્ટોરેજ નથી. ખાતરી કરો કે તમારી પાસે 250MB ખાલી સ્થાન છે અને ફરીથી પ્રારંભ કરો."</string>
@@ -1360,8 +1359,8 @@
     <string name="accept" msgid="5447154347815825107">"સ્વીકારો"</string>
     <string name="decline" msgid="6490507610282145874">"નકારો"</string>
     <string name="select_character" msgid="3352797107930786979">"અક્ષર શામેલ કરો"</string>
-    <string name="sms_control_title" msgid="4748684259903148341">"SMS સંદેશા મોકલી રહ્યું છે"</string>
-    <string name="sms_control_message" msgid="6574313876316388239">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; મોટા પ્રમાણમાં SMS સંદેશા મોકલી રહ્યું છે. શું તમે સંદેશા મોકલવાનું ચાલુ રાખવા માટે આ એપ્લિકેશનને મંજૂરી આપવા માગો છો?"</string>
+    <string name="sms_control_title" msgid="4748684259903148341">"SMS મેસેજ મોકલી રહ્યાં છે"</string>
+    <string name="sms_control_message" msgid="6574313876316388239">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; મોટા પ્રમાણમાં SMS મેસેજ મોકલી રહ્યું છે. શું તમે મેસેજ મોકલવાનું ચાલુ રાખવા માટે આ એપને મંજૂરી આપવા માગો છો?"</string>
     <string name="sms_control_yes" msgid="4858845109269524622">"મંજૂરી આપો"</string>
     <string name="sms_control_no" msgid="4845717880040355570">"નકારો"</string>
     <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; તમને &lt;b&gt;<xliff:g id="DEST_ADDRESS">%2$s</xliff:g>&lt;/b&gt; પર સંદેશ મોકલવા માગે છે."</string>
@@ -2037,7 +2036,7 @@
     <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Androidના કોઈ જૂના વર્ઝન માટે આ ઍપ બનાવવામાં આવી હતી. તે કદાચ યોગ્ય રીતે કામ કરતી નથી અને તેમાં નવીનતમ સુરક્ષા અને પ્રાઇવસી સંબંધિત સંરક્ષણો શામેલ નથી. કોઈ અપડેટ ચેક કરો અથવા ઍપના ડેવલપરનો સંપર્ક કરો."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"અપડેટ માટે તપાસો"</string>
     <string name="deprecated_abi_message" msgid="6820548011196218091">"આ ઍપ Androidના નવીનતમ વર્ઝન સાથે સુસંગત નથી. કોઈ અપડેટ ચેક કરો અથવા ઍપના ડેવલપરનો સંપર્ક કરો."</string>
-    <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા સંદેશા છે"</string>
+    <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા મેસેજ છે"</string>
     <string name="new_sms_notification_content" msgid="3197949934153460639">"જોવા માટે SMS ઍપ્લિકેશન ખોલો"</string>
     <string name="profile_encrypted_title" msgid="9001208667521266472">"કેટલીક કાર્યક્ષમતા મર્યાદિત હોઈ શકે છે"</string>
     <string name="profile_encrypted_detail" msgid="5279730442756849055">"કાર્યાલયની પ્રોફાઇલ લૉક કરી"</string>
@@ -2153,7 +2152,7 @@
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"ઓકે"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"બંધ કરો"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"વધુ જાણો"</string>
-    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Android 12માં Android માટે અનુકૂળ નોટિફિકેશનને બદલે વધુ સારા નોટિફિકેશન છે. આ સુવિધા સૂચિત ક્રિયાઓ અને જવાબો બતાવે છે તેમજ તમારા નોટિફિકેશનની યોગ્ય ગોઠવણી કરે છે.\n\nવધુ સારા નોટિફિકેશન સંપર્કોના નામ અને સંદેશા જેવી વ્યક્તિગત માહિતી સહિત નોટિફિકેશનનું બધું કન્ટેન્ટ ઍક્સેસ કરી શકે છે. આ સુવિધા ફોન કૉલના જવાબ આપવા કે \'ખલેલ પાડશો નહીં\'નું નિયંત્રણ કરવા જેવા નોટિફિકેશન છોડવાની કે તેનો જવાબ આપવાની ક્રિયા પણ કરી શકે છે."</string>
+    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Android 12માં Android માટે અનુકૂળ નોટિફિકેશનને બદલે વધુ સારા નોટિફિકેશન છે. આ સુવિધા સૂચિત ક્રિયાઓ અને જવાબો બતાવે છે તેમજ તમારા નોટિફિકેશનની યોગ્ય ગોઠવણી કરે છે.\n\nવધુ સારા નોટિફિકેશન સંપર્કોના નામ અને મેસેજ જેવી વ્યક્તિગત માહિતી સહિત નોટિફિકેશનનું બધું કન્ટેન્ટ ઍક્સેસ કરી શકે છે. આ સુવિધા ફોન કૉલના જવાબ આપવા કે \'ખલેલ પાડશો નહીં\'નું નિયંત્રણ કરવા જેવા નોટિફિકેશન છોડવાની કે તેનો જવાબ આપવાની ક્રિયા પણ કરી શકે છે."</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"રૂટિન મોડની માહિતીનું નોટિફિકેશન"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"બૅટરી સેવરની સુવિધા ચાલુ કરી છે"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"બૅટરીની આવરદા વધારવા માટે બૅટરીનો વપરાશ ઘટાડી રહ્યાં છીએ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 7bd182b..02369f9 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"वापस जाएं"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"इनपुट का तरीका बदलें"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"\'इनपुट का तरीका\' पिकर को खोलें"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"सेटिंग"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"मेमोरी में जगह नहीं बची है"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"हो सकता है कुछ सिस्टम फ़ंक्शन काम नहीं करें"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टम के लिए ज़रूरी मेमोरी नहीं है. पक्का करें कि आपके पास 250एमबी की खाली जगह है और फिर से शुरू करें."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e71e151..487d9e7 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Natrag"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Promjena načina unosa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otvori alat za odabir načina unosa"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Postavke"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ponestaje prostora za pohranu"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke sistemske funkcije možda neće raditi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno pohrane za sustav. Oslobodite 250 MB prostora i pokrenite uređaj ponovo."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 01d78a2..ca8787d 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Vissza"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Beviteli módszer váltása"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"A bevitelimód-választó megnyitása"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Beállítások"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Kevés a szabad terület"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Előfordulhat, hogy néhány rendszerfunkció nem működik."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nincs elegendő tárhely a rendszerhez. Győződjön meg arról, hogy rendelkezik 250 MB szabad területtel, majd kezdje elölről."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 9293a83..273e7745 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -525,7 +525,7 @@
     <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Այս հավելվածը կարող է հետզանգեր ստանալ՝ ցանկացած տեսախցիկի բացվելու (կնշվի բացող հավելվածը) և փակվելու դեպքում։"</string>
     <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Թույլատրել հավելվածին կամ ծառայությանը օգտագործել որպես միջերեսի համակարգային օգտատեր։"</string>
     <string name="permdesc_cameraHeadlessSystemUser" msgid="6963163319710996412">"Այս հավելվածին ձեր տեսախցիկը հասանելի է որպես առանց միջերեսի համակարգային օգտատեր։"</string>
-    <string name="permlab_vibrate" msgid="8596800035791962017">"կառավարել թրթռումը"</string>
+    <string name="permlab_vibrate" msgid="8596800035791962017">"կառավարել թրթռոցը"</string>
     <string name="permdesc_vibrate" msgid="8733343234582083721">"Թույլ է տալիս հավելվածին կառավարել թրթռոցը:"</string>
     <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Հավելվածին թույլ է տալիս օգտագործել սարքի թրթռալու ռեժիմը։"</string>
     <string name="permlab_callPhone" msgid="1798582257194643320">"ուղղակիորեն զանգել հեռախոսահամարներին"</string>
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Հետ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Փոխել ներածման եղանակը"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Բացել ներածման եղանակի ընտրիչը"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Կարգավորումներ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Հիշողությունը սպառվում է"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Որոշ գործառույթներ կարող են չաշխատել"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Համակարգի համար բավարար հիշողություն չկա: Համոզվեք, որ ունեք 250ՄԲ ազատ տարածություն և վերագործարկեք:"</string>
@@ -2424,7 +2423,7 @@
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Անվտանգության նկատառումներով՝ բովանդակությունը թաքցվել է ցուցադրումից"</string>
     <string name="satellite_notification_title" msgid="4026338973463121526">"Ավտոմատ միացել է արբանյակին"</string>
-    <string name="satellite_notification_summary" msgid="5207364139430767162">"Դուք կարող եք ուղարկել և ստանալ հաղորդագրություններ՝ առանց բջջային կամ Wi-Fi կապի"</string>
+    <string name="satellite_notification_summary" msgid="5207364139430767162">"Դուք կարող եք հաղորդագրություններ ուղարկել և ստանալ առանց բջջային կամ Wi-Fi կապի"</string>
     <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Օգտագործե՞լ արբանյակային հաղորդագրումը"</string>
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Ուղարկեք և ստացեք հաղորդագրություններ առանց բջջային կամ Wi-Fi ցանցի"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 3b1e037..47588ff 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Kembali"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Beralih metode input"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Buka pemilih metode input"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Setelan"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ruang penyimpanan hampir habis"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Beberapa fungsi sistem mungkin tidak dapat bekerja"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Penyimpanan tidak cukup untuk sistem. Pastikan Anda memiliki 250 MB ruang kosong, lalu mulai ulang."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 7bc4ddf..a833c8d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Til baka"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Skipta um innfærsluaðferð"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Opna val á innfærsluaðferð"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Stillingar"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Geymslurýmið er senn á þrotum"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sumir kerfiseiginleikar kunna að vera óvirkir"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Ekki nægt geymslurými fyrir kerfið. Gakktu úr skugga um að 250 MB séu laus og endurræstu."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index cb89354..662a9c0 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Indietro"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambia metodo di immissione"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Apri selettore metodo di immissione"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Impostazioni"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Spazio di archiviazione in esaurimento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Alcune funzioni di sistema potrebbero non funzionare"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Memoria insufficiente per il sistema. Assicurati di avere 250 MB di spazio libero e riavvia."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 41829df..37bffa4 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"חזרה"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"החלפה של שיטת הקלט"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"פתיחה של בוחר שיטות הקלט"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"הגדרות"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"מקום האחסון עומד להיגמר"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ייתכן שפונקציות מערכת מסוימות לא יפעלו"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"‏אין מספיק מקום אחסון עבור המערכת. עליך לוודא שיש לך מקום פנוי בנפח של 250MB ולהתחיל שוב."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 437a5e6..cce4abe 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"戻る"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"入力方法の切り替え"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"入力方法の選択ツールを開く"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"設定"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"空き容量わずか"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"一部のシステム機能が動作しない可能性があります"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"システムに十分な容量がありません。250MBの空き容量を確保して再起動してください。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index bc0ab8a..0d8b047 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"უკან"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"შეყვანის მეთოდის გადართვა"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"შეყვანის მეთოდის ამომრჩევის გახსნა"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"პარამეტრები"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"თავისუფალი ადგილი იწურება"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"სისტემის ზოგიერთმა ფუნქციამ შესაძლოა არ იმუშავოს"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"სისტემისათვის საკმარისი საცავი არ არის. დარწმუნდით, რომ იქონიოთ სულ მცირე 250 მბაიტი თავისუფალი სივრცე და დაიწყეთ ხელახლა."</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1574a78..72a2a3b 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Артқа"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Енгізу әдісін ауыстыру"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Енгізу әдісін таңдау құралын ашу"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Параметрлер"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Жадта орын азайып барады"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Жүйенің кейбір функциялары жұмыс істемеуі мүмкін"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Жүйе үшін жад жеткіліксіз. 250 МБ бос орын бар екенін тексеріп, қайта іске қосыңыз."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 4374bb6..2e5589e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ថយក្រោយ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ប្ដូរវិធីសាស្ត្រ​បញ្ចូល"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"បើក​ផ្ទាំងជ្រើសរើស​វិធីបញ្ចូល"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ការកំណត់"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"អស់​ទំហំ​ផ្ទុក"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"មុខងារ​ប្រព័ន្ធ​មួយ​ចំនួន​អាច​មិន​ដំណើរការ​"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"មិន​មាន​ទំហំ​ផ្ទុក​​គ្រប់​គ្រាន់​សម្រាប់​ប្រព័ន្ធ​។ សូម​ប្រាកដ​ថា​អ្នក​មាន​ទំហំ​ទំនេរ​ 250MB ហើយ​ចាប់ផ្ដើម​ឡើង​វិញ។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 00f22af..e5a5092 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ಹಿಂದಕ್ಕೆ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ಇನ್‌ಪುಟ್ ವಿಧಾನವನ್ನು ಬದಲಿಸಿ"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ಇನ್‌ಪುಟ್ ವಿಧಾನದ ಪಿಕರ್ ಅನ್ನು ತೆರೆಯಿರಿ"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ಸಂಗ್ರಹಣೆ ಸ್ಥಳವು ತುಂಬಿದೆ"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ಕೆಲವು ಸಿಸ್ಟಂ ಕಾರ್ಯವಿಧಾನಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ಸಿಸ್ಟಂನಲ್ಲಿ ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆಯಿಲ್ಲ. ನೀವು 250MB ನಷ್ಟು ಖಾಲಿ ಸ್ಥಳವನ್ನು ಹೊಂದಿರುವಿರಾ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಹಾಗೂ ಮರುಪ್ರಾರಂಭಿಸಿ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 3c38d5f..1413170 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"뒤로"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"입력 방법 전환"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"입력 방법 선택 도구 열기"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"설정"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"저장 공간이 부족함"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"일부 시스템 기능이 작동하지 않을 수 있습니다."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"시스템의 저장 공간이 부족합니다. 250MB의 여유 공간이 확보한 후 다시 시작하세요."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 1f29fa8..112bf0a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Артка"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Киргизүү ыкмасын өзгөртүү"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Киргизүү ыкмасын тандоо"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Параметрлер"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сактагычта орун калбай баратат"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Айрым функциялар иштебеши мүмкүн"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Системада сактагыч жетишсиз. 250МБ бош орун бар экенин текшерип туруп, өчүрүп күйгүзүңүз."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 3ea5e53..ba43481 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ກັບຄືນ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ສະຫຼັບວິທີການປ້ອນຂໍ້ມູນ"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ເປີດຕົວເລືອກວິທີການປ້ອນຂໍ້ມູນ"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ການຕັ້ງຄ່າ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນກຳລັງຈະເຕັມ"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ການເຮັດວຽກບາງຢ່າງຂອງລະບົບບາງອາດຈະໃຊ້ບໍ່ໄດ້"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"​ບໍ່​ມີ​ບ່ອນ​ເກັບ​ຂໍ້​ມູນ​ພຽງ​ພໍ​ສຳ​ລັບ​ລະ​ບົບ. ກວດ​ສອບ​ໃຫ້​ແນ່​ໃຈ​ວ່າ​ທ່ານ​ມີ​ພື້ນ​ທີ່​ຫວ່າງ​ຢ່າງ​ໜ້ອຍ 250MB ​ແລ້ວລອງ​ໃໝ່."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 78159b4..ad30d80 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atgal"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Perjungti įvesties metodą"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Atidaryti įvesties metodo rinkiklį"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Nustatymai"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Mažėja laisvos saugyklos vietos"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Kai kurios sistemos funkcijos gali neveikti"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistemos saugykloje nepakanka vietos. Įsitikinkite, kad yra 250 MB laisvos vietos, ir paleiskite iš naujo."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 84c69f1..329bbc3 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atpakaļ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Pārslēgt ievades metodi"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Atvērt ievades metodes atlasītāju"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Iestatījumi"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Paliek maz brīvas vietas"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Dažas sistēmas funkcijas var nedarboties."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistēmai pietrūkst vietas. Atbrīvojiet vismaz 250 MB vietas un restartējiet ierīci."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index e3e97a8..4bb0340 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Префрлете го методот за внесување"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Отворете го избирачот на метод за внесување"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Поставки"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Капацитетот е речиси полн"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некои системски функции може да не работат"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Нема доволно меморија во системот. Проверете дали има слободен простор од 250 MB и рестартирајте."</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index cc15d1e..72e522d 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"മടങ്ങുക"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ഇൻപുട്ട് രീതി മാറുക"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ഇൻപുട്ട് രീതി പിക്കർ തുറക്കുക"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ക്രമീകരണം"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"സംഭരണയിടം കഴിഞ്ഞു"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ചില സിസ്റ്റം പ്രവർത്തനങ്ങൾ പ്രവർത്തിക്കണമെന്നില്ല."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"സിസ്‌റ്റത്തിനായി മതിയായ സംഭരണമില്ല. 250MB സൗജന്യ സംഭരണമുണ്ടെന്ന് ഉറപ്പുവരുത്തി പുനരാരംഭിക്കുക."</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index a523fce..4398975 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Буцах"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Оруулах аргыг сэлгэх"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Оруулах арга сонгогчийг нээх"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Тохиргоо"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сангийн хэмжээ дутагдаж байна"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Зарим систем функц ажиллахгүй байна"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Системд хангалттай сан байхгүй байна. 250MБ чөлөөтэй зай байгаа эсэхийг шалгаад дахин эхлүүлнэ үү."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 37378b4..ac39d55 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"मागे जा"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"इनपुट पद्धत स्विच करा"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"इनपुट पद्धत पिकर उघडा"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"सेटिंग्ज"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"संचयन स्थान संपत आहे"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"काही सिस्टम कार्ये कार्य करू शकत नाहीत"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टीमसाठी पुरेसे संचयन नाही. आपल्याकडे 250MB मोकळे स्थान असल्याचे सुनिश्चित करा आणि रीस्टार्ट करा."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 41a64a0..88eef46 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Kembali"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Tukar kaedah masukan"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Buka pemilih kaedah input"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Tetapan"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ruang storan semakin berkurangan"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Beberapa fungsi sistem mungkin tidak berfungsi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tidak cukup storan untuk sistem. Pastikan anda mempunyai 250MB ruang kosong dan mulakan semula."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 229f1a1..d5c1fbd 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"နောက်သို့"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"လက်ကွက်ပြောင်းရန်"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"လက်ကွက်ရွေးစနစ် ဖွင့်ရန်"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ဆက်တင်များ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"သိမ်းဆည်သော နေရာ နည်းနေပါသည်"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"တချို့ စနစ်လုပ်ငန်းများ အလုပ် မလုပ်ခြင်း ဖြစ်နိုင်ပါသည်"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"စနစ်အတွက် သိုလှောင်ခန်း မလုံလောက်ပါ။ သင့်ဆီမှာ နေရာလွတ် ၂၅၀ MB ရှိတာ စစ်ကြည့်ပြီး စတင်ပါ။"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 5e39528..53b6ad5 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Tilbake"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Bytt inndatametode"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Åpne valg av inndatametode"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Innstillinger"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lite ledig lagringsplass"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontroller at du har 250 MB ledig plass, og start på nytt."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 056e253..2a23e87 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"पछाडि"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"इनपुट विधि बदल्नुहोस्"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"इनपुट विधि पिकर खोल्नुहोस्"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"सेटिङ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"भण्डारण ठाउँ सकिँदै छ"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"सायद केही प्रणाली कार्यक्रमहरूले काम गर्दैनन्"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"प्रणालीको लागि पर्याप्त भण्डारण छैन। तपाईँसँग २५० मेगा बाइट ठाउँ खाली भएको निश्चित गर्नुहोस् र फेरि सुरु गर्नुहोस्।"</string>
@@ -1443,7 +1442,7 @@
     <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"<xliff:g id="NAME">%s</xliff:g> अन्य एपहरूमा देखिँदैछ"</string>
     <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> अन्य एपहरूमा देखिँदैछ"</string>
     <string name="alert_windows_notification_message" msgid="6538171456970725333">"तपाईं <xliff:g id="NAME">%s</xliff:g> ले यो विशेषता प्रयोग नगरेको चाहनुहुन्न भने सेटिङहरू खोली यसलाई निष्क्रिय पार्न ट्याप गर्नुहोस्।"</string>
-    <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"निष्क्रिय पार्नुहोस्"</string>
+    <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"अफ गर्नुहोस्"</string>
     <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"जाँच गर्दै <xliff:g id="NAME">%s</xliff:g>…"</string>
     <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"हालको सामग्री समीक्षा गर्दै"</string>
     <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"मिडिया भण्डारणको जाँच गरिँदै छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index dc2675f..5106f7c 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Terug"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Invoermethode wijzigen"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Kiezer voor invoermethoden openen"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Instellingen"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Opslagruimte is bijna vol"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bepaalde systeemfuncties werken mogelijk niet"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Onvoldoende opslagruimte voor het systeem. Zorg ervoor dat je 250 MB vrije ruimte hebt en start opnieuw."</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 0ddb329..f9e9374 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ଇନପୁଟ ପଦ୍ଧତି ସ୍ୱିଚ କରନ୍ତୁ"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ଇନପୁଟ ପଦ୍ଧତି ପିକରକୁ ଖୋଲନ୍ତୁ"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ସେଟିଂସ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ଷ୍ଟୋରେଜ୍‌ ସ୍ପେସ୍‌ ଶେଷ ହେବାରେ ଲାଗିଛି"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"କିଛି ସିଷ୍ଟମ ପ୍ରକାର୍ଯ୍ୟ କାମ କରିନପାରେ"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ସିଷ୍ଟମ୍ ପାଇଁ ପ୍ରର୍ଯ୍ୟାପ୍ତ ଷ୍ଟୋରେଜ୍‌ ନାହିଁ। ସୁନିଶ୍ଚିତ କରନ୍ତୁ ଯେ, ଆପଣଙ୍କ ପାଖରେ 250MB ଖାଲି ଜାଗା ଅଛି ଏବଂ ପୁନଃ ଆରମ୍ଭ କରନ୍ତୁ।"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index ea616f6..e309832 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ਪਿੱਛੇ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ਇਨਪੁੱਟ ਵਿਧੀ ਨੂੰ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ਇਨਪੁੱਟ ਵਿਧੀ ਚੋਣਕਾਰ ਨੂੰ ਖੋਲ੍ਹੋ"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ਸਟੋਰੇਜ ਦੀ ਜਗ੍ਹਾ ਖਤਮ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ਕੁਝ ਸਿਸਟਮ ਫੰਕਸ਼ਨ ਕੰਮ ਨਹੀਂ ਵੀ ਕਰ ਸਕਦੇ"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ਸਿਸਟਮ ਲਈ ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੇ ਕੋਲ 250MB ਖਾਲੀ ਜਗ੍ਹਾ ਹੈ ਅਤੇ ਮੁੜ-ਚਾਲੂ ਕਰੋ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b302a2c..b6f70d6 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Wstecz"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Przełącz metodę wprowadzania"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otwórz selektor metody wprowadzania"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ustawienia"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Kończy się miejsce"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Niektóre funkcje systemu mogą nie działać"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Za mało pamięci w systemie. Upewnij się, że masz 250 MB wolnego miejsca i uruchom urządzenie ponownie."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 334f8c9..d5035e0 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Voltar"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Mudar o método de entrada"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir o seletor de método de entrada"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Configurações"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Pouco espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema podem não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1403,7 +1402,7 @@
     <string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI via USB ativado"</string>
     <string name="usb_uvc_notification_title" msgid="2030032862673400008">"Dispositivo conectado como Webcam"</string>
     <string name="usb_accessory_notification_title" msgid="1385394660861956980">"Acessório USB conectado"</string>
-    <string name="usb_notification_message" msgid="4715163067192110676">"Toque para conferir mais opções."</string>
+    <string name="usb_notification_message" msgid="4715163067192110676">"Toque para mais opções."</string>
     <string name="usb_power_notification_message" msgid="7284765627437897702">"Carregando dispositivo conectado. Toque para ver mais opções."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Acessório de áudio analógico detectado"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"O dispositivo anexo não é compatível com esse smartphone. Toque para saber mais."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 70fa9f9..a9ba018 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Voltar"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Alternar o método de introdução"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir o selecionador do método de introdução"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Definições"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Está quase sem espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema poderão não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não existe armazenamento suficiente para o sistema. Certifique-se de que tem 250 MB de espaço livre e reinicie."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 334f8c9..d5035e0 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Voltar"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Mudar o método de entrada"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Abrir o seletor de método de entrada"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Configurações"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Pouco espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema podem não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1403,7 +1402,7 @@
     <string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI via USB ativado"</string>
     <string name="usb_uvc_notification_title" msgid="2030032862673400008">"Dispositivo conectado como Webcam"</string>
     <string name="usb_accessory_notification_title" msgid="1385394660861956980">"Acessório USB conectado"</string>
-    <string name="usb_notification_message" msgid="4715163067192110676">"Toque para conferir mais opções."</string>
+    <string name="usb_notification_message" msgid="4715163067192110676">"Toque para mais opções."</string>
     <string name="usb_power_notification_message" msgid="7284765627437897702">"Carregando dispositivo conectado. Toque para ver mais opções."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Acessório de áudio analógico detectado"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"O dispositivo anexo não é compatível com esse smartphone. Toque para saber mais."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 12ad4f3..444dbd4 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Înapoi"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Schimbă metoda de introducere"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Deschide selectorul metodei de introducere a textului"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Setări"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Spațiul de stocare aproape ocupat"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Este posibil ca unele funcții de sistem să nu funcționeze"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Spațiu de stocare insuficient pentru sistem. Asigură-te că ai 250 MB de spațiu liber și repornește."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 0ae4778..46e7b9d 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Сменить способ ввода"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Выбрать способ ввода"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Настройки"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Недостаточно памяти"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некоторые функции могут не работать"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Недостаточно свободного места для системы. Освободите не менее 250 МБ дискового пространства и перезапустите устройство."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 4544ccb..fb03569 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ආපසු"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ආදාන ක්‍රමය මාරු කිරීම"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ආදාන ක්‍රම තෝරකය විවෘත කරන්න"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"සැකසීම්"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ආචයනය ඉඩ ප්‍රමාණය අඩු වී ඇත"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"සමහර පද්ධති කාර්යයන් ක්‍රියා නොකරනු ඇත"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"පද්ධතිය සඳහා ප්‍රමාණවත් ඉඩ නොමැත. ඔබට 250MB නිදහස් ඉඩක් තිබෙන ඔබට තිබෙන බව සහතික කරගෙන නැවත උත්සාහ කරන්න."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 359ca8f..bde470e 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Späť"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Prepnúť metódu vstupu"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Otvoriť výber metódy vstupu"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Nastavenia"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nedostatok ukladacieho priestoru"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Niektoré systémové funkcie nemusia fungovať"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"V úložisku nie je dostatok voľného miesta pre systém. Zaistite, aby ste mali 250 MB voľného miesta a zariadenie reštartujte."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 146a4c2..b70322b 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Nazaj"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Preklop načina vnosa"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Odpiranje izbirnika načina vnosa"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Nastavitve"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Prostor za shranjevanje bo pošel"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Nekatere sistemske funkcije morda ne delujejo"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"V shrambi ni dovolj prostora za sistem. Sprostite 250 MB prostora in znova zaženite napravo."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 896783a..3040597 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Pas"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Ndërro metodën e hyrjes"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Hap zgjedhësin e metodës së hyrjes"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Cilësimet"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Hapësira ruajtëse po mbaron"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Disa funksione të sistemit mund të mos punojnë"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nuk ka hapësirë të mjaftueshme ruajtjeje për sistemin. Sigurohu që të kesh 250 MB hapësirë të lirë dhe pastaj të rifillosh."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 4bcd833..f8e5a79 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1196,8 +1196,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Промените метод уноса"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Отвори бирач метода уноса"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Подешавања"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Меморијски простор је на измаку"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Неке системске функције можда не функционишу"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Нема довољно меморијског простора за систем. Уверите се да имате 250 MB слободног простора и поново покрените."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index edd5929..8e08c6b 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Tillbaka"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Byt inmatningsmetod"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Öppna inmatningsmetodsväljaren"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Inställningar"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lagringsutrymmet börjar ta slut"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Det kan hända att vissa systemfunktioner inte fungerar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det finns inte tillräckligt med utrymme för systemet. Kontrollera att du har ett lagringsutrymme på minst 250 MB och starta om."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 0c10e9c..a9385c1 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Rudi nyuma"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Badilisha mbinu ya kuingiza data"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Fungua kiteua mbinu ya kuingiza data"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Mipangilio"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nafasi ya kuhifadhi inakaribia kujaa"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Hifadhi haitoshi kwa ajili ya mfumo. Hakikisha una MB 250 za nafasi ya hifadhi isiyotumika na uanzishe upya."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 7bbe00a..6ba7a54 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"பின்செல்லும்"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"உள்ளீட்டு முறையை மாற்றும்"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"உள்ளீட்டு முறைத் தேர்வுக் கருவியைத் திறக்கும்"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"அமைப்புகள்"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"சேமிப்பிடம் குறைகிறது"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"சில அமைப்பு செயல்பாடுகள் வேலை செய்யாமல் போகலாம்"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"முறைமையில் போதுமான சேமிப்பகம் இல்லை. 250மெ.பை. அளவு காலி இடவசதி இருப்பதை உறுதிசெய்து மீண்டும் தொடங்கவும்."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 003d7e9..ae1a2c3 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"వెనుకకు"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ఇన్‌పుట్ విధానాన్ని మార్చండి"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"ఇన్‌పుట్ విధాన సెలెక్టర్‌ను తెరవండి"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"సెట్టింగ్‌లు"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"స్టోరేజ్‌ ఖాళీ అయిపోతోంది"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"కొన్ని సిస్టమ్ కార్యాచరణలు పని చేయకపోవచ్చు"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"సిస్టమ్ కోసం తగినంత స్టోరేజ్‌ లేదు. మీకు 250MB ఖాళీ స్థలం ఉందని నిర్ధారించుకుని, పునఃప్రారంభించండి."</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 19bd98b..655cf4c 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"กลับ"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"สลับวิธีการป้อนข้อมูล"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"เปิดเครื่องมือเลือกวิธีการป้อนข้อมูล"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"การตั้งค่า"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"พื้นที่จัดเก็บเหลือน้อย"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"บางฟังก์ชันระบบอาจไม่ทำงาน"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"พื้นที่เก็บข้อมูลไม่เพียงพอสำหรับระบบ โปรดตรวจสอบว่าคุณมีพื้นที่ว่าง 250 MB แล้วรีสตาร์ท"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index a53ca78..885be75 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Bumalik"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Magpalit ng pamamaraan ng pag-input"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Buksan ang picker ng pamamaraan ng pag-input"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Mga Setting"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nauubusan na ang puwang ng storage"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Maaaring hindi gumana nang tama ang ilang paggana ng system"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Walang sapat na storage para sa system. Tiyaking mayroon kang 250MB na libreng espasyo at i-restart."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 4c603e0..a5d064e 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Geri"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Giriş yöntemini değiştir"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Giriş yöntemi seçiciyi aç"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Ayarlar"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Depolama alanı bitiyor"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bazı sistem işlevleri çalışmayabilir"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistem için yeterli depolama alanı yok. 250 MB boş alanınızın bulunduğundan emin olun ve yeniden başlatın."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 1153830..5514ba4 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1197,8 +1197,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Змінити метод введення"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Відкрити засіб вибору методу введення"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Налаштування"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Закінчується пам’ять"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Деякі системні функції можуть не працювати"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Недостатньо місця для системи. Переконайтесь, що на пристрої є 250 МБ вільного місця, і повторіть спробу."</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 55a917d..7c6317c 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"پیچھے"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"اندراج کا طریقہ سوئچ کریں"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"اندراج کے طریقے کا منتخب کنندہ کھولیں"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"ترتیبات"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"اسٹوریج کی جگہ ختم ہو رہی ہے"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"ممکن ہے سسٹم کے کچھ فنکشنز کام نہ کریں"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"‏سسٹم کیلئے کافی اسٹوریج نہیں ہے۔ اس بات کو یقینی بنائیں کہ آپ کے پاس 250MB خالی جگہ ہے اور دوبارہ شروع کریں۔"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index f2451d5..868a927 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Orqaga"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Matn kiritish usulini almashtirish"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Kiritish usulini tanlash oynasini ochish"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Sozlamalar"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Xotirada joy yetarli emas"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Ayrim funksiyalar ishlamasligi mumkin"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tizim uchun xotirada joy yetarli emas. Avval 250 megabayt joy bo‘shatib, keyin qurilmani o‘chirib yoqing."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 7c354a4..c7337ef 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Quay lại"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Chuyển phương thức nhập"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Mở bộ chọn phương thức nhập"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Cài đặt"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Sắp hết dung lượng lưu trữ"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Một số chức năng hệ thống có thể không hoạt động"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Không đủ bộ nhớ cho hệ thống. Đảm bảo bạn có 250 MB dung lượng trống và khởi động lại."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 1b8dd3a..378e548 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"返回"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"切换输入法"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"打开输入法选择器"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"设置"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"存储空间不足"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"某些系统功能可能无法正常使用"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系统存储空间不足。请确保您有250MB的可用空间,然后重新启动。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 91ccc07..5a9db4f 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"返回"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"切換輸入方法"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"打開輸入方法點選器"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"設定"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"儲存空間即將用盡"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"部分系統功能可能無法運作"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系統儲存空間不足。請確認裝置有 250 MB 的可用空間,然後重新啟動。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 8f5ae19..5cfca16 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"返回"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"切換輸入法"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"開啟輸入法挑選器"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"設定"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"儲存空間即將用盡"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"部分系統功能可能無法運作"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系統儲存空間不足。請確定你已釋出 250MB 的可用空間,然後重新啟動。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index f09e922..c8e49a1 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1195,8 +1195,7 @@
     <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Emuva"</string>
     <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Shintsha indlela yokufaka"</string>
     <string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"Vula okokukhetha kwendlela yokufaka"</string>
-    <!-- no translation found for input_method_switcher_settings_button (5609835654697108485) -->
-    <skip />
+    <string name="input_method_switcher_settings_button" msgid="5609835654697108485">"Amasethingi"</string>
     <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Isikhala sokulondoloza siyaphela"</string>
     <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Eminye imisebenzi yohlelo ingahle ingasebenzi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Akusona isitoreji esanele sesistimu. Qiniseka ukuthi unesikhala esikhululekile esingu-250MB uphinde uqalise kabusha."</string>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index a7240ff..69437b4 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -440,10 +440,6 @@
     <string name="config_satellite_carrier_roaming_esos_provisioned_class" translatable="false"></string>
     <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_class" />
 
-    <!-- Telephony satellite gateway intent for handling carrier roaming to satellite is using ESOS messaging. -->
-    <string name="config_satellite_carrier_roaming_esos_provisioned_intent_action" translatable="false"></string>
-    <java-symbol type="string" name="config_satellite_carrier_roaming_esos_provisioned_intent_action" />
-
     <!-- The time duration in minutes to wait before retry validating a possible change
          in satellite allowed region. The default value is 10 minutes. -->
     <integer name="config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region">10</integer>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c084b4c..dc99634 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1504,26 +1504,26 @@
 
     <!-- @hide -->
     <style name="PointerIconVectorStyleFillGreen">
-        <item name="pointerIconVectorFill">#6DD58C</item>
-        <item name="pointerIconVectorFillInverse">#6DD58C</item>
+        <item name="pointerIconVectorFill">#1AA64A</item>
+        <item name="pointerIconVectorFillInverse">#1AA64A</item>
     </style>
 
     <!-- @hide -->
     <style name="PointerIconVectorStyleFillYellow">
-        <item name="pointerIconVectorFill">#FDD663</item>
-        <item name="pointerIconVectorFillInverse">#FDD663</item>
+        <item name="pointerIconVectorFill">#F55E57</item>
+        <item name="pointerIconVectorFillInverse">#F55E57</item>
     </style>
 
     <!-- @hide -->
     <style name="PointerIconVectorStyleFillPink">
-        <item name="pointerIconVectorFill">#F2B8B5</item>
-        <item name="pointerIconVectorFillInverse">#F2B8B5</item>
+        <item name="pointerIconVectorFill">#F94AAB</item>
+        <item name="pointerIconVectorFillInverse">#F94AAB</item>
     </style>
 
     <!-- @hide -->
     <style name="PointerIconVectorStyleFillBlue">
-        <item name="pointerIconVectorFill">#8AB4F8</item>
-        <item name="pointerIconVectorFillInverse">#8AB4F8</item>
+        <item name="pointerIconVectorFill">#009DC9</item>
+        <item name="pointerIconVectorFillInverse">#009DC9</item>
     </style>
 
     <!-- @hide -->
@@ -1535,7 +1535,7 @@
     <!-- @hide -->
     <style name="PointerIconVectorStyleStrokeBlack">
         <item name="pointerIconVectorStroke">@color/black</item>
-        <item name="pointerIconVectorStrokeInverse">@color/white</item>
+        <item name="pointerIconVectorStrokeInverse">@color/black</item>
     </style>
 
     <!-- @hide -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a52bd4..74922ac 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5466,6 +5466,8 @@
   <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
   <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
   <java-symbol type="xml" name="haptic_feedback_customization" />
+  <java-symbol type="xml" name="haptic_feedback_customization_source_rotary_encoder" />
+  <java-symbol type="xml" name="haptic_feedback_customization_source_touchscreen" />
 
   <!-- For ActivityManager PSS profiling configurability -->
   <java-symbol type="bool" name="config_am_disablePssProfiling" />
@@ -5564,6 +5566,7 @@
   <java-symbol type="string"  name="recs_notification_channel_label"/>
 
   <!-- Priority Modes icons -->
+  <java-symbol type="drawable" name="ic_zen_priority_modes" />
   <java-symbol type="drawable" name="ic_zen_mode_type_bedtime" />
   <java-symbol type="drawable" name="ic_zen_mode_type_driving" />
   <java-symbol type="drawable" name="ic_zen_mode_type_immersive" />
@@ -5573,6 +5576,7 @@
   <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" />
   <java-symbol type="drawable" name="ic_zen_mode_type_theater" />
   <java-symbol type="drawable" name="ic_zen_mode_type_unknown" />
+  <java-symbol type="drawable" name="ic_zen_mode_type_special_dnd" />
 
   <!-- System notification for background user sound -->
   <java-symbol type="string" name="bg_user_sound_notification_title_alarm" />
diff --git a/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
new file mode 100644
index 0000000..7ac0787
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
@@ -0,0 +1,18 @@
+<?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.
+  -->
+
+<haptic-feedback-constants/>
diff --git a/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
new file mode 100644
index 0000000..7ac0787
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
@@ -0,0 +1,18 @@
+<?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.
+  -->
+
+<haptic-feedback-constants/>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
index 94bde68..127dbfd 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.BATTERY_STATS"/>
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
@@ -31,7 +32,8 @@
         <activity android:name=".BatteryConsumerPickerActivity"
                   android:label="Battery Stats"
                   android:launchMode="singleTop"
-                  android:exported="true">
+                  android:exported="true"
+                  android:enabled="false">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -41,5 +43,25 @@
         <activity android:name=".BatteryStatsViewerActivity"
                   android:label="Battery Stats"
                   android:parentActivityName=".BatteryConsumerPickerActivity"/>
+
+        <activity android:name=".TrampolineActivity"
+            android:exported="true"
+            android:theme="@android:style/Theme.NoDisplay">
+            <intent-filter>
+                <action android:name="com.android.settings.action.IA_SETTINGS"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <meta-data android:name="com.android.settings.category"
+                android:value="com.android.settings.category.ia.development" />
+            <meta-data android:name="com.android.settings.title"
+                android:resource="@string/settings_title" />
+            <meta-data android:name="com.android.settings.summary"
+                android:resource="@string/settings_summary" />
+            <meta-data android:name="com.android.settings.group_key"
+                android:value="debug_debugging_category" />
+            <meta-data android:name="com.android.settings.order"
+                android:value="2" />
+        </activity>
     </application>
 </manifest>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
new file mode 100644
index 0000000..c23c148
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settings_title">Launch Battery Stats Viewer</string>
+    <string name="settings_summary">The Battery Stats Viewer will be visible in the Launcher after it is opened once.</string>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
new file mode 100644
index 0000000..b016488
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.batterystatsviewer;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+public class TrampolineActivity extends Activity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        showLauncherIcon();
+        launchMainActivity();
+    }
+
+    private void showLauncherIcon() {
+        PackageManager pm = getPackageManager();
+        pm.setComponentEnabledSetting(new ComponentName(this, BatteryConsumerPickerActivity.class),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    private void launchMainActivity() {
+        startActivity(new Intent(this, BatteryConsumerPickerActivity.class));
+    }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index edf461a..b0e48f1 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -803,3 +803,11 @@
     include_annotations: ["android.platform.test.annotations.PlatinumTest"],
     exclude_annotations: FLAKY_OR_IGNORED,
 }
+
+test_module_config {
+    name: "FrameworksCoreTests_android_tracing",
+    base: "FrameworksCoreTests",
+    team: "trendy_team_windowing_tools",
+    test_suites: ["device-tests"],
+    include_filters: ["android.tracing"],
+}
diff --git a/core/tests/coretests/src/android/tracing/TEST_MAPPING b/core/tests/coretests/src/android/tracing/TEST_MAPPING
new file mode 100644
index 0000000..4b7adf9
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "postsubmit": [
+    {
+       "name": "FrameworksCoreTests_android_tracing",
+        "file_patterns": [".*\\.java"]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
index 14e4e20..c9df83d 100644
--- a/core/tests/coretests/src/android/util/StateSetTest.java
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,7 +32,6 @@
  * Tests for {@link StateSet}
  */
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = StateSet.class)
 public class StateSetTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 668487d..786f1e8 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -40,6 +40,7 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl.Transaction;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
+import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -117,11 +118,21 @@
         SparseArray<InsetsSourceControl> controls = new SparseArray<>();
         controls.put(ID_STATUS_BAR, topConsumer.getControl());
         controls.put(ID_NAVIGATION_BAR, navConsumer.getControl());
+        InsetsAnimationSpec spec = new InsetsAnimationSpec() {
+            @Override
+            public long getDurationMs(boolean hasZeroInsetsIme) {
+                return 10;
+            }
+            @Override
+            public Interpolator getInsetsInterpolator(boolean hasZeroInsetsIme) {
+                return new LinearInterpolator();
+            }
+        };
+
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
-                mMockController, 10 /* durationMs */, new LinearInterpolator(),
-                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
-                null /* statsToken */);
+                mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
+                0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
         mController.setReadyDispatched(true);
     }
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index a5137bdf..6e563ff 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 43;
+    private static final int NUM_MARSHALLED_PROPERTIES = 44;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/widget/ChronometerTest.java b/core/tests/coretests/src/android/widget/ChronometerTest.java
index 3c73837..cb33117 100644
--- a/core/tests/coretests/src/android/widget/ChronometerTest.java
+++ b/core/tests/coretests/src/android/widget/ChronometerTest.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.app.Activity;
+import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
 
 import androidx.test.filters.LargeTest;
@@ -28,7 +29,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * Test {@link DatePicker} focus changes.
+ * Test {@link Chronometer} counting up and down.
  */
 @SuppressWarnings("deprecation")
 @LargeTest
@@ -50,26 +51,48 @@
     }
 
     public void testChronometerTicksSequentially() throws Throwable {
-        final CountDownLatch latch = new CountDownLatch(5);
+        final CountDownLatch latch = new CountDownLatch(6);
         ArrayList<String> ticks = new ArrayList<>();
         runOnUiThread(() -> {
             mChronometer.setOnChronometerTickListener((chronometer) -> {
                 ticks.add(chronometer.getText().toString());
                 latch.countDown();
                 try {
-                    Thread.sleep(500);
+                    Thread.sleep(250);
                 } catch (InterruptedException e) {
                 }
             });
             mChronometer.start();
         });
-        assertTrue(latch.await(6, TimeUnit.SECONDS));
-        assertTrue(ticks.size() >= 5);
+        assertTrue(latch.await(5500, TimeUnit.MILLISECONDS));
         assertEquals("00:00", ticks.get(0));
         assertEquals("00:01", ticks.get(1));
         assertEquals("00:02", ticks.get(2));
         assertEquals("00:03", ticks.get(3));
         assertEquals("00:04", ticks.get(4));
+        assertEquals("00:05", ticks.get(5));
+    }
+
+    public void testChronometerCountDown() throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(5);
+        ArrayList<String> ticks = new ArrayList<>();
+        runOnUiThread(() -> {
+            mChronometer.setBase(SystemClock.elapsedRealtime() + 3_000);
+            mChronometer.setCountDown(true);
+            mChronometer.post(() -> {
+                mChronometer.setOnChronometerTickListener((chronometer) -> {
+                    ticks.add(chronometer.getText().toString());
+                    latch.countDown();
+                });
+                mChronometer.start();
+            });
+        });
+        assertTrue(latch.await(4500, TimeUnit.MILLISECONDS));
+        assertEquals("00:02", ticks.get(0));
+        assertEquals("00:01", ticks.get(1));
+        assertEquals("00:00", ticks.get(2));
+        assertEquals("−00:01", ticks.get(3));
+        assertEquals("−00:02", ticks.get(4));
     }
 
     private void runOnUiThread(Runnable runnable) throws InterruptedException {
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index bcf1053..3e76977 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -40,6 +41,9 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.ContextMenu;
 import android.view.MenuItem;
 import android.view.textclassifier.TextClassification;
@@ -47,9 +51,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.verification.VerificationMode;
 
 /**
  * TextViewTest tests {@link TextView}.
@@ -86,6 +93,10 @@
 
     private SelectionActionModeHelper mMockHelper;
 
+    @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE =
+            new SetFlagsRule.ClassRule();
+    @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule();
+
     @Before
     public void setUp() {
         mMockHelper = mock(SelectionActionModeHelper.class);
@@ -234,6 +245,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
     public void testAutofillMenuItemEnabledWhenNoTextSelected() {
         ContextMenu menu = mock(ContextMenu.class);
         MenuItem mockMenuItem = newMockMenuItem();
@@ -254,6 +266,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
     public void testAutofillMenuItemNotEnabledWhenTextSelected() {
         ContextMenu menu = mock(ContextMenu.class);
         MenuItem mockMenuItem = newMockMenuItem();
@@ -271,4 +284,147 @@
         verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
         verify(mockAutofillMenuItem).setEnabled(false);
     }
+
+    private interface EditTextSetup {
+        void run(EditText et);
+    }
+
+    private void verifyMenuItemNotAdded(EditTextSetup setup, int id, VerificationMode times) {
+        ContextMenu menu = mock(ContextMenu.class);
+        MenuItem mockMenuItem = newMockMenuItem();
+        when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem);
+        EditText et = spy(new EditText(getInstrumentation().getContext()));
+        setup.run(et);
+        Editor editor = new Editor(et);
+        editor.setTextContextMenuItems(menu);
+        verify(menu, times).add(anyInt(), eq(id), anyInt(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuUndoNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canUndo(),
+                TextView.ID_UNDO, never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuUndoAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canUndo(), TextView.ID_UNDO,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuRedoNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRedo(), TextView.ID_REDO,
+                never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuRedoAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canRedo(), TextView.ID_REDO,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuCutNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCut(), TextView.ID_CUT,
+                never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuCutAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCut(), TextView.ID_CUT,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuCopyNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCopy(), TextView.ID_COPY,
+                never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuCopyAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCopy(), TextView.ID_COPY,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuPasteNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPaste(), TextView.ID_PASTE,
+                never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuPasteAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPaste(), TextView.ID_PASTE,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuPasteAsPlaintextNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPasteAsPlainText(),
+                        TextView.ID_PASTE_AS_PLAIN_TEXT, never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuPasteAsPlaintextAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPasteAsPlainText(),
+                        TextView.ID_PASTE_AS_PLAIN_TEXT, times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuSelectAllNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canSelectAllText(),
+                        TextView.ID_SELECT_ALL, never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuSelectAllAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canSelectAllText(),
+                        TextView.ID_SELECT_ALL, times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuShareNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canShare(), TextView.ID_SHARE,
+                never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuShareAddedWhenAvailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canShare(), TextView.ID_SHARE,
+                times(1));
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuAutofillNotAddedWhenUnavailable() {
+        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRequestAutofill(),
+                TextView.ID_AUTOFILL, never());
+    }
+
+    @Test
+    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
+    public void testContextMenuAutofillNotAddedWhenUnavailableBecauseTextSelected() {
+        verifyMenuItemNotAdded((spy) -> {
+            doReturn(true).when(spy).canRequestAutofill();
+            doReturn("test").when(spy).getSelectedText();
+        }, TextView.ID_AUTOFILL, never());
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 499caf5..c3a5b19c94 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -359,7 +359,7 @@
         tracker.end(FrameTracker.REASON_END_NORMAL);
 
         // Send incomplete callback for 102L
-        sendSfFrame(tracker, 102L, JANK_NONE);
+        sendSfFrame(tracker, 4, 102L, JANK_NONE);
 
         // Send janky but complete callbck fo 103L
         sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
@@ -629,7 +629,7 @@
         if (!tracker.mSurfaceOnly) {
             sendHwuiFrame(tracker, durationMillis, vsyncId, firstWindowFrame);
         }
-        sendSfFrame(tracker, vsyncId, jankType);
+        sendSfFrame(tracker, durationMillis, vsyncId, jankType);
     }
 
     private void sendHwuiFrame(FrameTracker tracker, long durationMillis, long vsyncId,
@@ -645,11 +645,13 @@
         captor.getValue().run();
     }
 
-    private void sendSfFrame(FrameTracker tracker, long vsyncId, @JankType int jankType) {
+    private void sendSfFrame(
+            FrameTracker tracker, long durationMillis, long vsyncId, @JankType int jankType) {
         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(tracker).postCallback(captor.capture());
         mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
-                new JankData(vsyncId, jankType, FRAME_TIME_60Hz)
+                new JankData(vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
+                TimeUnit.MILLISECONDS.toNanos(durationMillis))
         });
         captor.getValue().run();
     }
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
index a895378..b183ecb 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
 import android.os.Parcel;
 import android.os.UserHandle;
 
@@ -37,18 +39,55 @@
      */
     @Test
     public void testParcelable() {
+        final StatusBarIcon original = newStatusBarIcon();
+
+        final StatusBarIcon copy = parcelAndUnparcel(original);
+
+        assertSerializableFieldsEqual(copy, original);
+    }
+
+    @Test
+    public void testClone_withPreloaded() {
+        final StatusBarIcon original = newStatusBarIcon();
+        original.preloadedIcon = new ColorDrawable(Color.RED);
+
+        final StatusBarIcon copy = original.clone();
+
+        assertSerializableFieldsEqual(copy, original);
+        assertThat(copy.preloadedIcon).isNotNull();
+        assertThat(copy.preloadedIcon).isInstanceOf(ColorDrawable.class);
+        assertThat(((ColorDrawable) copy.preloadedIcon).getColor()).isEqualTo(Color.RED);
+    }
+
+    @Test
+    public void testClone_noPreloaded() {
+        final StatusBarIcon original = newStatusBarIcon();
+
+        final StatusBarIcon copy = original.clone();
+
+        assertSerializableFieldsEqual(copy, original);
+        assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon);
+    }
+
+
+    private static StatusBarIcon newStatusBarIcon() {
         final UserHandle dummyUserHandle = UserHandle.of(100);
         final String dummyIconPackageName = "com.android.internal.statusbar.test";
         final int dummyIconId = 123;
         final int dummyIconLevel = 1;
         final int dummyIconNumber = 2;
         final CharSequence dummyIconContentDescription = "dummyIcon";
-        final StatusBarIcon original = new StatusBarIcon(dummyIconPackageName, dummyUserHandle,
-                dummyIconId, dummyIconLevel, dummyIconNumber, dummyIconContentDescription,
+        return new StatusBarIcon(
+                dummyIconPackageName,
+                dummyUserHandle,
+                dummyIconId,
+                dummyIconLevel,
+                dummyIconNumber,
+                dummyIconContentDescription,
                 StatusBarIcon.Type.SystemIcon);
+    }
 
-        final StatusBarIcon copy = clone(original);
-
+    private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) {
         assertThat(copy.user).isEqualTo(original.user);
         assertThat(copy.pkg).isEqualTo(original.pkg);
         assertThat(copy.icon.sameAs(original.icon)).isTrue();
@@ -56,19 +95,17 @@
         assertThat(copy.visible).isEqualTo(original.visible);
         assertThat(copy.number).isEqualTo(original.number);
         assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
+        assertThat(copy.type).isEqualTo(original.type);
     }
 
-    private StatusBarIcon clone(StatusBarIcon original) {
-        Parcel parcel = null;
+    private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) {
+        Parcel parcel = Parcel.obtain();
         try {
-            parcel = Parcel.obtain();
             original.writeToParcel(parcel, 0);
             parcel.setDataPosition(0);
             return StatusBarIcon.CREATOR.createFromParcel(parcel);
         } finally {
-            if (parcel != null) {
-                parcel.recycle();
-            }
+            parcel.recycle();
         }
     }
 }
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index cbaac21..4edf52b 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -71,16 +71,9 @@
     },
 }
 
-// TODO(nona): Change this to use generate_font_fallback to be able to generate XML from
-//             per family JSON config
-prebuilt_fonts_xml {
+prebuilt_etc {
     name: "font_fallback.xml",
-    src: "font_fallback.xml",
-    soong_config_variables: {
-        use_var_font: {
-            src: "font_fallback_cjkvf.xml",
-        },
-    },
+    src: ":generate_font_fallback",
 }
 
 /////////////////////////////////
diff --git a/data/keyboards/Vendor_0957_Product_0033.idc b/data/keyboards/Vendor_0957_Product_0033.idc
new file mode 100644
index 0000000..7dfbe2c
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0033.idc
@@ -0,0 +1,23 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Input Device Configuration file for Google Reference RCU Remote.
+# PID 0033 is for new G20 with start button.
+
+# Basic Parameters
+keyboard.layout = Vendor_0957_Product_0031
+# The reason why we set is follow https://docs.partner.android.com/tv/build/gtv/boot-resume
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 69a68c8..0726624 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -80,9 +81,14 @@
         }
 
         if (DEBUG) Log.d(TAG, "Start to back up " + taskContainers);
+        final List<ParcelableTaskContainerData> parcelableTaskContainerDataList = new ArrayList<>(
+                taskContainers.size());
+        for (TaskContainer taskContainer : taskContainers) {
+            parcelableTaskContainerDataList.add(taskContainer.getParcelableData());
+        }
         final Bundle state = new Bundle();
-        state.setClassLoader(TaskContainer.class.getClassLoader());
-        state.putParcelableList(KEY_TASK_CONTAINERS, taskContainers);
+        state.setClassLoader(ParcelableTaskContainerData.class.getClassLoader());
+        state.putParcelableList(KEY_TASK_CONTAINERS, parcelableTaskContainerDataList);
         mController.setSavedState(state);
     }
 
@@ -91,10 +97,12 @@
             return;
         }
 
-        final List<TaskContainer> taskContainers = savedState.getParcelableArrayList(
-                KEY_TASK_CONTAINERS, TaskContainer.class);
-        for (TaskContainer taskContainer : taskContainers) {
-            if (DEBUG) Log.d(TAG, "restore task " + taskContainer.getTaskId());
+        final List<ParcelableTaskContainerData> parcelableTaskContainerDataList =
+                savedState.getParcelableArrayList(KEY_TASK_CONTAINERS,
+                        ParcelableTaskContainerData.class);
+        for (ParcelableTaskContainerData data : parcelableTaskContainerDataList) {
+            final TaskContainer taskContainer = new TaskContainer(data, mController);
+            if (DEBUG) Log.d(TAG, "Restoring task " + taskContainer.getTaskId());
             // TODO(b/289875940): implement the TaskContainer restoration.
         }
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
new file mode 100644
index 0000000..817cfce
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * This class holds the Parcelable data of a {@link SplitContainer}.
+ */
+class ParcelableSplitContainerData implements Parcelable {
+
+    /**
+     * A reference to the target {@link SplitContainer} that owns the data. This will not be
+     * parcelled and will be {@code null} when the data is created from a parcel.
+     */
+    @Nullable
+    final SplitContainer mSplitContainer;
+
+    @NonNull
+    final IBinder mToken;
+
+    @NonNull
+    private final IBinder mPrimaryContainerToken;
+
+    @NonNull
+    private final IBinder mSecondaryContainerToken;
+
+    // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule.
+    @Nullable
+    final String mSplitRuleTag;
+
+    /**
+     * Whether the selection of which container is primary can be changed at runtime. Runtime
+     * updates is currently possible only for {@link SplitPinContainer}
+     *
+     * @see SplitPinContainer
+     */
+    final boolean mIsPrimaryContainerMutable;
+
+    ParcelableSplitContainerData(@NonNull SplitContainer splitContainer, @NonNull IBinder token,
+            @NonNull IBinder primaryContainerToken, @NonNull IBinder secondaryContainerToken,
+            @Nullable String splitRuleTag, boolean isPrimaryContainerMutable) {
+        mSplitContainer = splitContainer;
+        mToken = token;
+        mPrimaryContainerToken = primaryContainerToken;
+        mSecondaryContainerToken = secondaryContainerToken;
+        mSplitRuleTag = splitRuleTag;
+        mIsPrimaryContainerMutable = isPrimaryContainerMutable;
+    }
+
+    private ParcelableSplitContainerData(Parcel in) {
+        mSplitContainer = null;
+        mToken = in.readStrongBinder();
+        mPrimaryContainerToken = in.readStrongBinder();
+        mSecondaryContainerToken = in.readStrongBinder();
+        mSplitRuleTag = in.readString();
+        mIsPrimaryContainerMutable = in.readBoolean();
+    }
+
+    public static final Creator<ParcelableSplitContainerData> CREATOR = new Creator<>() {
+        @Override
+        public ParcelableSplitContainerData createFromParcel(Parcel in) {
+            return new ParcelableSplitContainerData(in);
+        }
+
+        @Override
+        public ParcelableSplitContainerData[] newArray(int size) {
+            return new ParcelableSplitContainerData[size];
+        }
+    };
+
+    @NonNull
+    private IBinder getPrimaryContainerToken() {
+        return mSplitContainer != null ? mSplitContainer.getPrimaryContainer().getToken()
+                : mPrimaryContainerToken;
+    }
+
+    @NonNull
+    private IBinder getSecondaryContainerToken() {
+        return mSplitContainer != null ? mSplitContainer.getSecondaryContainer().getToken()
+                : mSecondaryContainerToken;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeStrongBinder(getPrimaryContainerToken());
+        dest.writeStrongBinder(getSecondaryContainerToken());
+        dest.writeString(mSplitRuleTag);
+        dest.writeBoolean(mIsPrimaryContainerMutable);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java
new file mode 100644
index 0000000..7377d00
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskContainerData.java
@@ -0,0 +1,127 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class holds the Parcelable data of a {@link TaskContainer}.
+ */
+class ParcelableTaskContainerData implements Parcelable {
+
+    /**
+     * A reference to the target {@link TaskContainer} that owns the data. This will not be
+     * parcelled and will be {@code null} when the data is created from a parcel.
+     */
+    @Nullable
+    final TaskContainer mTaskContainer;
+
+    /**
+     * The unique task id.
+     */
+    final int mTaskId;
+
+    /**
+     * The parcelable data of the active TaskFragmentContainers in this Task.
+     * Note that this will only be populated before parcelling, and will not be copied when
+     * making a new instance copy.
+     */
+    @NonNull
+    private final List<ParcelableTaskFragmentContainerData>
+            mParcelableTaskFragmentContainerDataList = new ArrayList<>();
+
+    /**
+     * The parcelable data of the SplitContainers in this Task.
+     * Note that this will only be populated before parcelling, and will not be copied when
+     * making a new instance copy.
+     */
+    @NonNull
+    private final List<ParcelableSplitContainerData> mParcelableSplitContainerDataList =
+            new ArrayList<>();
+
+    ParcelableTaskContainerData(int taskId, @NonNull TaskContainer taskContainer) {
+        if (taskId == INVALID_TASK_ID) {
+            throw new IllegalArgumentException("Invalid Task id");
+        }
+
+        mTaskId = taskId;
+        mTaskContainer = taskContainer;
+    }
+
+    ParcelableTaskContainerData(@NonNull ParcelableTaskContainerData data,
+            @NonNull TaskContainer taskContainer) {
+        mTaskId = data.mTaskId;
+        mTaskContainer = taskContainer;
+    }
+
+    private ParcelableTaskContainerData(Parcel in) {
+        mTaskId = in.readInt();
+        mTaskContainer = null;
+        in.readParcelableList(mParcelableTaskFragmentContainerDataList,
+                ParcelableTaskFragmentContainerData.class.getClassLoader(),
+                ParcelableTaskFragmentContainerData.class);
+        in.readParcelableList(mParcelableSplitContainerDataList,
+                ParcelableSplitContainerData.class.getClassLoader(),
+                ParcelableSplitContainerData.class);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mTaskId);
+        dest.writeParcelableList(getParcelableTaskFragmentContainerDataList(), flags);
+        dest.writeParcelableList(getParcelableSplitContainerDataList(), flags);
+    }
+
+    @NonNull
+    List<? extends ParcelableTaskFragmentContainerData>
+            getParcelableTaskFragmentContainerDataList() {
+        return mTaskContainer != null ? mTaskContainer.getParcelableTaskFragmentContainerDataList()
+                : mParcelableTaskFragmentContainerDataList;
+    }
+
+    @NonNull
+    List<? extends ParcelableSplitContainerData> getParcelableSplitContainerDataList() {
+        return mTaskContainer != null ? mTaskContainer.getParcelableSplitContainerDataList()
+                : mParcelableSplitContainerDataList;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ParcelableTaskContainerData> CREATOR = new Creator<>() {
+        @Override
+        public ParcelableTaskContainerData createFromParcel(Parcel in) {
+            return new ParcelableTaskContainerData(in);
+        }
+
+        @Override
+        public ParcelableTaskContainerData[] newArray(int size) {
+            return new ParcelableTaskContainerData[size];
+        }
+    };
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
new file mode 100644
index 0000000..a79a89a
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * This class holds the Parcelable data of a {@link TaskFragmentContainer}.
+ */
+class ParcelableTaskFragmentContainerData implements Parcelable {
+
+    /**
+     * Client-created token that uniquely identifies the task fragment container instance.
+     */
+    @NonNull
+    final IBinder mToken;
+
+    /**
+     * The tag specified in launch options. {@code null} if this taskFragment container is not an
+     * overlay container.
+     */
+    @Nullable
+    final String mOverlayTag;
+
+    /**
+     * The associated {@link Activity#getActivityToken()} of the overlay container.
+     * Must be {@code null} for non-overlay container.
+     * <p>
+     * If an overlay container is associated with an activity, this overlay container will be
+     * dismissed when the associated activity is destroyed. If the overlay container is visible,
+     * activity will be launched on top of the overlay container and expanded to fill the parent
+     * container.
+     */
+    @Nullable
+    final IBinder mAssociatedActivityToken;
+
+    /**
+     * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
+     */
+    @NonNull
+    final Rect mLastRequestedBounds;
+
+    ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag,
+            @Nullable IBinder associatedActivityToken) {
+        mToken = token;
+        mOverlayTag = overlayTag;
+        mAssociatedActivityToken = associatedActivityToken;
+        mLastRequestedBounds = new Rect();
+    }
+
+    private ParcelableTaskFragmentContainerData(Parcel in) {
+        mToken = in.readStrongBinder();
+        mOverlayTag = in.readString();
+        mAssociatedActivityToken = in.readStrongBinder();
+        mLastRequestedBounds = in.readTypedObject(Rect.CREATOR);
+    }
+
+    public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() {
+        @Override
+        public ParcelableTaskFragmentContainerData createFromParcel(Parcel in) {
+            return new ParcelableTaskFragmentContainerData(in);
+        }
+
+        @Override
+        public ParcelableTaskFragmentContainerData[] newArray(int size) {
+            return new ParcelableTaskFragmentContainerData[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeString(mOverlayTag);
+        dest.writeStrongBinder(mAssociatedActivityToken);
+        dest.writeTypedObject(mLastRequestedBounds, flags);
+    }
+
+}
+
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 39cface..6d436ec 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -33,6 +33,8 @@
  */
 class SplitContainer {
     @NonNull
+    private final ParcelableSplitContainerData mParcelableData;
+    @NonNull
     private TaskFragmentContainer mPrimaryContainer;
     @NonNull
     private final TaskFragmentContainer mSecondaryContainer;
@@ -44,16 +46,6 @@
     /** @see SplitContainer#getDefaultSplitAttributes() */
     @NonNull
     private SplitAttributes mDefaultSplitAttributes;
-    @NonNull
-    private final IBinder mToken;
-
-    /**
-     * Whether the selection of which container is primary can be changed at runtime. Runtime
-     * updates is currently possible only for {@link SplitPinContainer}
-     *
-     * @see SplitPinContainer
-     */
-    private final boolean mIsPrimaryContainerMutable;
 
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
@@ -69,13 +61,14 @@
             @NonNull TaskFragmentContainer secondaryContainer,
             @NonNull SplitRule splitRule,
             @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
+        mParcelableData = new ParcelableSplitContainerData(this, new Binder("SplitContainer"),
+                primaryContainer.getToken(), secondaryContainer.getToken(), splitRule.getTag(),
+                isPrimaryContainerMutable);
         mPrimaryContainer = primaryContainer;
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
         mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
         mCurrentSplitAttributes = splitAttributes;
-        mToken = new Binder("SplitContainer");
-        mIsPrimaryContainerMutable = isPrimaryContainerMutable;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -94,7 +87,7 @@
     }
 
     void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
-        if (!mIsPrimaryContainerMutable) {
+        if (!mParcelableData.mIsPrimaryContainerMutable) {
             throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
         }
         mPrimaryContainer = primaryContainer;
@@ -150,7 +143,12 @@
 
     @NonNull
     IBinder getToken() {
-        return mToken;
+        return mParcelableData.mToken;
+    }
+
+    @NonNull
+    ParcelableSplitContainerData getParcelableData() {
+        return mParcelableData;
     }
 
     /**
@@ -201,7 +199,7 @@
             return null;
         }
         return new SplitInfo(primaryActivityStack, secondaryActivityStack,
-                mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mToken));
+                mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mParcelableData.mToken));
     }
 
     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index fb8efc4..5637320 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -169,7 +169,7 @@
         mController = controller;
         final Bundle outSavedState = new Bundle();
         if (Flags.aeBackStackRestore()) {
-            outSavedState.setClassLoader(TaskContainer.class.getClassLoader());
+            outSavedState.setClassLoader(ParcelableTaskContainerData.class.getClassLoader());
             registerOrganizer(false /* isSystemOrganizer */, outSavedState);
         } else {
             registerOrganizer();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 5795e8d..82dfda5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -16,7 +16,6 @@
 
 package androidx.window.extensions.embedding;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -32,8 +31,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -57,11 +54,12 @@
 import java.util.Set;
 
 /** Represents TaskFragments and split pairs below a Task. */
-class TaskContainer implements Parcelable {
+class TaskContainer {
     private static final String TAG = TaskContainer.class.getSimpleName();
 
-    /** The unique task id. */
-    private final int mTaskId;
+    /** Parcelable data of this TaskContainer. */
+    @NonNull
+    private final ParcelableTaskContainerData mParcelableTaskContainerData;
 
     /** Active TaskFragments in this Task. */
     @NonNull
@@ -130,11 +128,9 @@
      * @param splitController The {@link SplitController}.
      */
     TaskContainer(int taskId, @NonNull Activity activityInTask,
-            @Nullable SplitController splitController) {
-        if (taskId == INVALID_TASK_ID) {
-            throw new IllegalArgumentException("Invalid Task id");
-        }
-        mTaskId = taskId;
+            @NonNull SplitController splitController) {
+        mParcelableTaskContainerData = new ParcelableTaskContainerData(taskId, this);
+
         final TaskProperties taskProperties = TaskProperties
                 .getTaskPropertiesFromActivity(activityInTask);
         mInfo = new TaskFragmentParentInfo(
@@ -148,8 +144,44 @@
         mSplitController = splitController;
     }
 
+    /** This is only used when restoring it from a {@link ParcelableTaskContainerData}. */
+    TaskContainer(@NonNull ParcelableTaskContainerData data,
+            @NonNull SplitController splitController) {
+        mParcelableTaskContainerData = new ParcelableTaskContainerData(data, this);
+        mSplitController = splitController;
+        for (ParcelableTaskFragmentContainerData tfData :
+                data.getParcelableTaskFragmentContainerDataList()) {
+            final TaskFragmentContainer container =
+                    new TaskFragmentContainer(tfData, splitController, this);
+            mContainers.add(container);
+        }
+    }
+
+    @NonNull
+    ParcelableTaskContainerData getParcelableData() {
+        return mParcelableTaskContainerData;
+    }
+
+    @NonNull
+    List<ParcelableTaskFragmentContainerData> getParcelableTaskFragmentContainerDataList() {
+        final List<ParcelableTaskFragmentContainerData> data = new ArrayList<>(mContainers.size());
+        for (TaskFragmentContainer container : mContainers) {
+            data.add(container.getParcelableData());
+        }
+        return data;
+    }
+
+    @NonNull
+    List<ParcelableSplitContainerData> getParcelableSplitContainerDataList() {
+        final List<ParcelableSplitContainerData> data = new ArrayList<>(mSplitContainers.size());
+        for (SplitContainer splitContainer : mSplitContainers) {
+            data.add(splitContainer.getParcelableData());
+        }
+        return data;
+    }
+
     int getTaskId() {
-        return mTaskId;
+        return mParcelableTaskContainerData.mTaskId;
     }
 
     int getDisplayId() {
@@ -680,34 +712,6 @@
         return activityStacks;
     }
 
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mTaskId);
-        // TODO(b/289875940)
-    }
-
-    protected TaskContainer(Parcel in) {
-        mTaskId = in.readInt();
-        // TODO(b/289875940)
-    }
-
-    public static final Creator<TaskContainer> CREATOR = new Creator<>() {
-        @Override
-        public TaskContainer createFromParcel(Parcel in) {
-            return new TaskContainer(in);
-        }
-
-        @Override
-        public TaskContainer[] newArray(int size) {
-            return new TaskContainer[size];
-        }
-    };
-
     /** A wrapper class which contains the information of {@link TaskContainer} */
     static final class TaskProperties {
         private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index dc6506b..dc1d983 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -53,15 +53,13 @@
 class TaskFragmentContainer {
     private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;
 
+    /** Parcelable data of this TaskFragmentContainer. */
+    @NonNull
+    private final ParcelableTaskFragmentContainerData mParcelableData;
+
     @NonNull
     private final SplitController mController;
 
-    /**
-     * Client-created token that uniquely identifies the task fragment container instance.
-     */
-    @NonNull
-    private final IBinder mToken;
-
     /** Parent leaf Task. */
     @NonNull
     private final TaskContainer mTaskContainer;
@@ -103,9 +101,6 @@
      */
     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
 
-    @Nullable
-    private final String mOverlayTag;
-
     /**
      * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
      * for {@link #isOverlay()} container.
@@ -113,29 +108,13 @@
     @NonNull
     private final Bundle mLaunchOptions = new Bundle();
 
-    /**
-     * The associated {@link Activity#getActivityToken()} of the overlay container.
-     * Must be {@code null} for non-overlay container.
-     * <p>
-     * If an overlay container is associated with an activity, this overlay container will be
-     * dismissed when the associated activity is destroyed. If the overlay container is visible,
-     * activity will be launched on top of the overlay container and expanded to fill the parent
-     * container.
-     */
-    @Nullable
-    private final IBinder mAssociatedActivityToken;
-
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
     /**
-     * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
-     */
-    private final Rect mLastRequestedBounds = new Rect();
-
-    /**
      * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
      */
+    // TODO(b/289875940): review this and other field that might need to be moved in the base class.
     @WindowingMode
     private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
 
@@ -208,17 +187,17 @@
             @NonNull SplitController controller,
             @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
             @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
+        mParcelableData = new ParcelableTaskFragmentContainerData(
+                new Binder("TaskFragmentContainer"), overlayTag,
+                associatedActivity != null ? associatedActivity.getActivityToken() : null);
+
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
                     "One and only one of pending activity and intent must be non-null");
         }
         mController = controller;
-        mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
-        mOverlayTag = overlayTag;
-        mAssociatedActivityToken = associatedActivity != null
-                ? associatedActivity.getActivityToken() : null;
 
         if (launchOptions != null) {
             mLaunchOptions.putAll(launchOptions);
@@ -259,18 +238,26 @@
         if (overlayTag != null && pendingAppearedIntent != null
                 && associatedActivity != null && !associatedActivity.isFinishing()) {
             final IBinder associatedActivityToken = associatedActivity.getActivityToken();
-            final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
-                    launchOptions, pendingAppearedIntent);
+            final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(
+                    mParcelableData.mToken, launchOptions, pendingAppearedIntent);
             mController.mOverlayRestoreParams.put(associatedActivityToken, params);
         }
     }
 
+    /** This is only used when restoring it from a {@link ParcelableTaskFragmentContainerData}. */
+    TaskFragmentContainer(@NonNull ParcelableTaskFragmentContainerData data,
+            @NonNull SplitController splitController, @NonNull TaskContainer taskContainer) {
+        mParcelableData = data;
+        mController = splitController;
+        mTaskContainer = taskContainer;
+    }
+
     /**
      * Returns the client-created token that uniquely identifies this container.
      */
     @NonNull
     IBinder getTaskFragmentToken() {
-        return mToken;
+        return mParcelableData.mToken;
     }
 
     /** List of non-finishing activities that belong to this container and live in this process. */
@@ -389,7 +376,8 @@
             return null;
         }
         return new ActivityStack(activities, isEmpty(),
-                ActivityStack.Token.createFromBinder(mToken), mOverlayTag);
+                ActivityStack.Token.createFromBinder(mParcelableData.mToken),
+                mParcelableData.mOverlayTag);
     }
 
     /** Adds the activity that will be reparented to this container. */
@@ -413,7 +401,7 @@
         final ActivityThread.ActivityClientRecord record = ActivityThread
                 .currentActivityThread().getActivityClient(activityToken);
         if (record != null) {
-            record.mTaskFragmentToken = mToken;
+            record.mTaskFragmentToken = mParcelableData.mToken;
         }
     }
 
@@ -469,7 +457,7 @@
         if (!isOverlayWithActivityAssociation()) {
             return;
         }
-        if (mAssociatedActivityToken == activityToken) {
+        if (mParcelableData.mAssociatedActivityToken == activityToken) {
             // If the associated activity is destroyed, also finish this overlay container.
             mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
         }
@@ -776,8 +764,8 @@
      * @see WindowContainerTransaction#setRelativeBounds
      */
     boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
-        return (relBounds == null && mLastRequestedBounds.isEmpty())
-                || mLastRequestedBounds.equals(relBounds);
+        return (relBounds == null && mParcelableData.mLastRequestedBounds.isEmpty())
+                || mParcelableData.mLastRequestedBounds.equals(relBounds);
     }
 
     /**
@@ -787,14 +775,14 @@
      */
     void setLastRequestedBounds(@Nullable Rect relBounds) {
         if (relBounds == null) {
-            mLastRequestedBounds.setEmpty();
+            mParcelableData.mLastRequestedBounds.setEmpty();
         } else {
-            mLastRequestedBounds.set(relBounds);
+            mParcelableData.mLastRequestedBounds.set(relBounds);
         }
     }
 
     @NonNull Rect getLastRequestedBounds() {
-        return mLastRequestedBounds;
+        return mParcelableData.mLastRequestedBounds;
     }
 
     /**
@@ -965,6 +953,16 @@
         return mTaskContainer.getTaskId();
     }
 
+    @NonNull
+    IBinder getToken() {
+        return mParcelableData.mToken;
+    }
+
+    @NonNull
+    ParcelableTaskFragmentContainerData getParcelableData() {
+        return mParcelableData;
+    }
+
     /** Gets the parent Task. */
     @NonNull
     TaskContainer getTaskContainer() {
@@ -1011,7 +1009,7 @@
 
     /** Returns whether this taskFragment container is an overlay container. */
     boolean isOverlay() {
-        return mOverlayTag != null;
+        return mParcelableData.mOverlayTag != null;
     }
 
     /**
@@ -1020,7 +1018,7 @@
      */
     @Nullable
     String getOverlayTag() {
-        return mOverlayTag;
+        return mParcelableData.mOverlayTag;
     }
 
     /**
@@ -1045,7 +1043,7 @@
      */
     @Nullable
     IBinder getAssociatedActivityToken() {
-        return mAssociatedActivityToken;
+        return mParcelableData.mAssociatedActivityToken;
     }
 
     /**
@@ -1053,11 +1051,11 @@
      * a non-fill-parent overlay without activity association.
      */
     boolean isAlwaysOnTopOverlay() {
-        return isOverlay() && mAssociatedActivityToken == null;
+        return isOverlay() && mParcelableData.mAssociatedActivityToken == null;
     }
 
     boolean isOverlayWithActivityAssociation() {
-        return isOverlay() && mAssociatedActivityToken != null;
+        return isOverlay() && mParcelableData.mAssociatedActivityToken != null;
     }
 
     @Override
@@ -1074,13 +1072,13 @@
     private String toString(boolean includeContainersToFinishOnExit) {
         return "TaskFragmentContainer{"
                 + " parentTaskId=" + getTaskId()
-                + " token=" + mToken
+                + " token=" + mParcelableData.mToken
                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
                 + " runningActivityCount=" + getRunningActivityCount()
                 + " isFinished=" + mIsFinished
-                + " overlayTag=" + mOverlayTag
-                + " associatedActivityToken=" + mAssociatedActivityToken
-                + " lastRequestedBounds=" + mLastRequestedBounds
+                + " overlayTag=" + mParcelableData.mOverlayTag
+                + " associatedActivityToken=" + mParcelableData.mAssociatedActivityToken
+                + " lastRequestedBounds=" + mParcelableData.mLastRequestedBounds
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
                 + containersToFinishOnExitToString() : "")
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 08d0dd3..d1b98a6 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Maak kieslys oop"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gryp skerm vas"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Hierdie app se grootte kan nie verander word nie"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 9efbcb5..8044719 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"ምናሌን ክፈት"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ማያ ገጹን አሳድግ"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ይህ መተግበሪያ መጠኑ ሊቀየር አይችልም"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 433d99a..21aa34e 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"فتح القائمة"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"التقاط صورة للشاشة"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"لا يمكن تغيير حجم نافذة هذا التطبيق"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 47e78f5..c59f470 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খোলক"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্ৰীন স্নেপ কৰক"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"এই এপ্‌টোৰ আকাৰ সলনি কৰিব নোৱাৰি"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 380a34a..841323e 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menyunu açın"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranı çəkin"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu tətbiqin ölçüsünü dəyişmək olmur"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index b09c7b1..86ab548 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otvorite meni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Uklopi ekran"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Veličina ove aplikacije ne može da se promeni"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index f5945b1..bcbc1ae 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Адкрыць меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Размясціць на палавіне экрана"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Немагчыма змяніць памер праграмы"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 6b1f385..bf8bc99 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খুলুন"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্রিনে অ্যাপ মানানসই হিসেবে ছোট বড় করুন"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"এই অ্যাপ ছোট বড় করা যাবে না"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 6bc5274..cf53d25 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje menija"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nije moguće promijeniti veličinu aplikacije"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 5dd65dd..87ea62e 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Obre el menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajusta la pantalla"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No es pot canviar la mida d\'aquesta aplicació"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index b75dbfc..e21213b 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otevřít nabídku"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikost aplikace nelze změnit"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 40ad859..1c4647f 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Åbn menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tilpas skærm"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Størrelsen på denne app kan ikke justeres"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 9389f3e..88a5789 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menü öffnen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Bildschirm teilen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Die Größe dieser App kann nicht geändert werden"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index a9a14bf..beeefee 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Άνοιγμα μενού"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Προβολή στο μισό της οθόνης"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Δεν είναι δυνατή η αλλαγή μεγέθους αυτής της εφαρμογής"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 78a2c9e..72f4070 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 78a2c9e..72f4070 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 78a2c9e..72f4070 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 8dadc70..5756aae 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Abrir el menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No se puede cambiar el tamaño de esta app"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 33eba93..3c55bf6 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No se puede cambiar el tamaño de esta aplicación"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index e76f6e8..d921967 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Ava menüü"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Kuva poolel ekraanil"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Selle rakenduse aknasuurust ei saa muuta"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 961c784..f319af1 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Ireki menua"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zatitu pantaila"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ezin zaio aldatu tamaina aplikazio honi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index c004ea8..44a0929 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"باز کردن منو"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"بزرگ کردن صفحه"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"اندازه این برنامه را نمی‌توان تغییر داد"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 288dbb2..02f832b 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aligner l\'écran"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Impossible de redimensionner cette appli"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 7a7e592..5d916f4 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fractionner l\'écran"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Impossible de redimensionner cette appli"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 8f3b132..e1b2a7e 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar pantalla"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Non se pode cambiar o tamaño desta aplicación"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 78eebd6..fecce73 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"મેનૂ ખોલો"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"સ્ક્રીન સ્નૅપ કરો"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"આ ઍપના કદમાં વધઘટ કરી શકાતો નથી"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index dca2431..04053c8 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje izbornika"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Izradi snimku zaslona"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nije moguće promijeniti veličinu aplikacije"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index a0e9ec5..bb52649 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menü megnyitása"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Igazodás a képernyő adott részéhez"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ezt az alkalmazást nem lehet átméretezni"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 2208a5c..fff5a10 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Բացել ընտրացանկը"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ծալել էկրանը"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Այս հավելվածի չափը հնարավոր չէ փոխել"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 11de817..a957754 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gabungkan Layar"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ukuran aplikasi ini tidak dapat diubah"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index ad679a8..7b91768 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Opna valmynd"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Smelluskjár"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ekki er hægt að breyta stærð þessa forrits"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index cccb71f..ea73653 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"פתיחת התפריט"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"כיווץ המסך"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"לא ניתן לשנות את גודל החלון של האפליקציה הזו"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index a46cc81..c6f558f 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Мәзірді ашу"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды бөлу"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Бұл қолданбаның өлшемі өзгертілмейді."</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 1187567..508ea48 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"បើកម៉ឺនុយ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ថតអេក្រង់"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"មិនអាចប្ដូរទំហំ​កម្មវិធីនេះ​បានទេ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index c1ae69a..1fc627b 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -38,16 +38,16 @@
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್‌ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
     <string name="accessibility_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
     <string name="divider_title" msgid="1963391955593749442">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
-    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ಎಡ ಪೂರ್ಣ ಪರದೆ"</string>
+    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ಎಡ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% ಎಡಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% ಎಡಕ್ಕೆ"</string>
-    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ಬಲ ಪೂರ್ಣ ಪರದೆ"</string>
-    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ಮೇಲಿನ ಪೂರ್ಣ ಪರದೆ"</string>
+    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ಬಲ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
+    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ಮೇಲಿನ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% ಮೇಲಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% ಮೇಲಕ್ಕೆ"</string>
-    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ಕೆಳಗಿನ ಪೂರ್ಣ ಪರದೆ"</string>
+    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ಕೆಳಗಿನ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="accessibility_split_left" msgid="1713683765575562458">"ಎಡಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
     <string name="accessibility_split_right" msgid="8441001008181296837">"ಬಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
     <string name="accessibility_split_top" msgid="2789329702027147146">"ಮೇಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"ಮೆನು ತೆರೆಯಿರಿ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ಸ್ನ್ಯಾಪ್ ಸ್ಕ್ರೀನ್"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಗಾತ್ರಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 3dfe573..efb7930 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -20,7 +20,7 @@
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string>
     <string name="pip_close" msgid="2955969519031223530">"ಮುಚ್ಚಿರಿ"</string>
-    <string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string>
+    <string name="pip_fullscreen" msgid="7278047353591302554">"ಫುಲ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="pip_move" msgid="158770205886688553">"ಸರಿಸಿ"</string>
     <string name="pip_expand" msgid="1051966011679297308">"ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"ಕುಗ್ಗಿಸಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 5ff159d..96d360e 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"메뉴 열기"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"화면 분할"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"이 앱은 크기를 조절할 수 없습니다."</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 958a239..662c2eae 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Менюну ачуу"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды сүрөткө тартып алуу"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Бул колдонмонун өлчөмүн өзгөртүүгө болбойт"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 86fc0d8..f71d650 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Atidaryti meniu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Negalima keisti šios programos dydžio"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index a16cc52..abadef7 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Atvērt izvēlni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fiksēt ekrānu"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Šīs lietotnes loga lielumu nevar mainīt."</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 2878c45..0576fc0 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Отвори го менито"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Подели го екранот на половина"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Не може да се промени големината на апликацијава"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index e64edb1..d69ec05 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Цэс нээх"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Дэлгэцийг таллах"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Энэ аппын хэмжээг өөрчлөх боломжгүй"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 14a6b0e..33ba1c2 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"मेनू उघडा"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रीन स्नॅप करा"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"या अ‍ॅपचा आकार बदलला जाऊ शकत नाही"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 7e80c84..e024e4b 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Apl ini tidak boleh diubah saiz"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 32f3234..bd680b4 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"မီနူး ဖွင့်ရန်"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"စခရင်ကို ချုံ့မည်"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ဤအက်ပ်ကို အရွယ်ပြင်၍ မရပါ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index f965a50..896d9fd 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fest skjermen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Du kan ikke endre størrelse på denne appen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 32c87d5..a9c06fb 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menu openen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Scherm halveren"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Het formaat van deze app kan niet worden aangepast"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 9151dea..a80cfc2 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -66,7 +66,7 @@
     <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"ତଳ ବାମକୁ ନିଅନ୍ତୁ"</string>
     <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
     <string name="bubble_accessibility_action_expand_menu" msgid="8637233525952938845">"ମେନୁକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
-    <string name="bubble_accessibility_action_collapse_menu" msgid="2975310870146231463">"ମେନୁକୁ ସଂକୁଚିତ କରନ୍ତୁ"</string>
+    <string name="bubble_accessibility_action_collapse_menu" msgid="2975310870146231463">"ମେନୁକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
     <string name="bubble_accessibility_action_move_bar_left" msgid="4803535120353716759">"ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="bubble_accessibility_action_move_bar_right" msgid="7686542531917510421">"ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"<xliff:g id="BUBBLE_TITLE">%1$s</xliff:g> ବିସ୍ତାର କରନ୍ତୁ"</string>
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ସ୍କ୍ରିନକୁ ସ୍ନାପ କରନ୍ତୁ"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ଏହି ଆପକୁ ରିସାଇଜ କରାଯାଇପାରିବ ନାହିଁ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index e099cc9..7257161 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ਸਕ੍ਰੀਨ ਨੂੰ ਸਨੈਪ ਕਰੋ"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ਇਸ ਐਪ ਦਾ ਆਕਾਰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index f954c9d..7600db0 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otwórz menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nie można zmienić rozmiaru tej aplikacji"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 1e98015..58c78f3 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar o app"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 1e98015..58c78f3 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar o app"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 0b96492..077503a 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Deschide meniul"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Micșorează fereastra și fixeaz-o"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Aplicația nu poate fi redimensionată"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index a1b39e4..5471027 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Открыть меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Свернуть"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Изменить размер приложения нельзя."</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 1b70ffc..3f015f6 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"මෙනුව විවෘත කරන්න"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ස්නැප් තිරය"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"මෙම යෙදුම ප්‍රතිප්‍රමාණ කළ නොහැක"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 344e3c7..fa376e7 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Otvoriť ponuku"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zobraziť polovicu obrazovky"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Veľkosť tejto aplikácie sa nedá zmeniť"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 118831d..8538668 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Odpri meni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikosti te aplikacije ni mogoče spremeniti"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 7976af0..f77a43d 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Hap menynë"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Regjistro ekranin"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Përmasat e këtij aplikacioni nuk mund të ndryshohen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 6243c52..af7686a 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Отворите мени"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Уклопи екран"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Величина ове апликације не може да се промени"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 1584a3d..0d08d8d 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Öppna menyn"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fäst skärmen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Det går inte att ändra storlek på appen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index bf6af0c..448f6249 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Fungua Menyu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Panga Madirisha kwenye Skrini"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Huwezi kubadilisha ukubwa wa programu hii"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 825fc8f..1268929 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"மெனுவைத் திற"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"திரையை ஸ்னாப் செய்"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"இந்த ஆப்ஸின் அளவை மாற்ற முடியாது"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 17f3322..524e558 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"మెనూను తెరవండి"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"స్క్రీన్‌ను స్నాప్ చేయండి"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ఈ యాప్ సైజ్‌ను మార్చడం సాధ్యపడదు"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index c03600f..00a395f 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"เปิดเมนู"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"สแนปหน้าจอ"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ปรับขนาดแอปนี้ไม่ได้"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 174c524..50a9211 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Buksan ang Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"I-snap ang Screen"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Hindi nare-resize ang app na ito"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 0ae2a6a..ddd4206 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menüyü Aç"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranın Yarısına Tuttur"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu uygulama yeniden boyutlandırılamaz"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 64ca28c..1dcdfe6 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Відкрити меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Зафіксувати екран"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Розмір вікна цього додатка не можна змінити"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 19cef43..26ece5c 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"مینو کھولیں"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"اسکرین کا اسناپ شاٹ لیں"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"اس ایپ کا سائز تبدیل نہیں کیا جا سکتا"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 6768e07..90b9a3f 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Menyuni ochish"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranni biriktirish"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu ilova hajmini oʻzgartirish imkonsiz"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index eef1e8e..90471f9 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Mở Trình đơn"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Điều chỉnh kích thước màn hình"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Không thể đổi kích thước của ứng dụng này"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index b152c0a..0aa52ac 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"打开菜单"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"屏幕快照"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"无法调整此应用的大小"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index d96739f..8a5be6a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"打開選單"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"此應用程式無法調整大小"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 6bc93e6..d1cc4bb 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"開啟選單"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"這個應用程式無法調整大小"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index e152705..6163a97 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -127,6 +127,5 @@
     <string name="expand_menu_text" msgid="3847736164494181168">"Vula Imenyu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Thwebula Isikrini"</string>
-    <!-- no translation found for desktop_mode_non_resizable_snap_text (1049800446363800707) -->
-    <skip />
+    <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Le app ayikwazi ukushintshwa usayizi"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 47d5274..424d4bf 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -48,7 +48,7 @@
     TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
     SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
     DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
-    DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
+    DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false),
     ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
     BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
     EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d7da051..33949f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1066,14 +1066,30 @@
         return true;
     }
 
+    private void kickStartAnimation() {
+        startSystemAnimation();
+
+        // Dispatch the first progress after animation start for
+        // smoothing the initial animation, instead of waiting for next
+        // onMove.
+        final BackMotionEvent backFinish = mCurrentTracker
+                .createProgressEvent();
+        dispatchOnBackProgressed(mActiveCallback, backFinish);
+        if (!mBackGestureStarted) {
+            // if the down -> up gesture happened before animation
+            // start, we have to trigger the uninterruptible transition
+            // to finish the back animation.
+            startPostCommitAnimation();
+        }
+    }
+
     private void createAdapter() {
         IBackAnimationRunner runner =
                 new IBackAnimationRunner.Stub() {
                     @Override
                     public void onAnimationStart(
                             RemoteAnimationTarget[] apps,
-                            RemoteAnimationTarget[] wallpapers,
-                            RemoteAnimationTarget[] nonApps,
+                            IBinder token,
                             IBackAnimationFinishedCallback finishedCallback) {
                         mShellExecutor.execute(
                                 () -> {
@@ -1085,21 +1101,12 @@
                                     }
                                     mBackAnimationFinishedCallback = finishedCallback;
                                     mApps = apps;
-                                    startSystemAnimation();
-                                    mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
-
-                                    // Dispatch the first progress after animation start for
-                                    // smoothing the initial animation, instead of waiting for next
-                                    // onMove.
-                                    final BackMotionEvent backFinish = mCurrentTracker
-                                            .createProgressEvent();
-                                    dispatchOnBackProgressed(mActiveCallback, backFinish);
-                                    if (!mBackGestureStarted) {
-                                        // if the down -> up gesture happened before animation
-                                        // start, we have to trigger the uninterruptible transition
-                                        // to finish the back animation.
-                                        startPostCommitAnimation();
+                                    // app only visible after transition ready, break for now.
+                                    if (token != null) {
+                                        return;
                                     }
+                                    kickStartAnimation();
+                                    mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
                                 });
                     }
 
@@ -1199,6 +1206,9 @@
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
+            if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+                kickStartAnimation();
+            }
             // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
             // need to post to ShellExecutor when called.
             if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
@@ -1382,6 +1392,7 @@
             }
             // Handle the commit transition if this handler is running the open transition.
             finishCallback.onTransitionFinished(null);
+            t.apply();
             if (mCloseTransitionRequested) {
                 if (mApps == null || mApps.length == 0) {
                     if (mQueuedTransition == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index c2ee223..972b78f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,6 +39,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener;
@@ -67,6 +68,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.IntPredicate;
 import java.util.function.Predicate;
 
 /**
@@ -189,6 +191,9 @@
     @NonNull
     private final CompatUIStatusManager mCompatUIStatusManager;
 
+    @NonNull
+    private final IntPredicate mInDesktopModePredicate;
+
     public CompatUIController(@NonNull Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -202,7 +207,8 @@
             @NonNull CompatUIConfiguration compatUIConfiguration,
             @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
             @NonNull AccessibilityManager accessibilityManager,
-            @NonNull CompatUIStatusManager compatUIStatusManager) {
+            @NonNull CompatUIStatusManager compatUIStatusManager,
+            @NonNull IntPredicate isDesktopModeEnablePredicate) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -218,6 +224,7 @@
         mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
                 DISAPPEAR_DELAY_MS, flags);
         mCompatUIStatusManager = compatUIStatusManager;
+        mInDesktopModePredicate = isDesktopModeEnablePredicate;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -251,7 +258,9 @@
             updateActiveTaskInfo(taskInfo);
         }
 
-        if (taskInfo.configuration == null || taskListener == null) {
+        // We close all the Compat UI educations in case we're in desktop mode.
+        if (taskInfo.configuration == null || taskListener == null
+                || isInDesktopMode(taskInfo.displayId)) {
             // Null token means the current foreground activity is not in compatibility mode.
             removeLayouts(taskInfo.taskId);
             return;
@@ -350,7 +359,6 @@
         mOnInsetsChangedListeners.remove(displayId);
     }
 
-
     @Override
     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
         updateDisplayLayout(displayId);
@@ -692,7 +700,8 @@
         mContext.startActivityAsUser(intent, userHandle);
     }
 
-    private void removeLayouts(int taskId) {
+    @VisibleForTesting
+    void removeLayouts(int taskId) {
         final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
         if (compatLayout != null) {
             compatLayout.release();
@@ -825,4 +834,9 @@
         boolean mHasShownCameraCompatHint;
         boolean mHasShownUserAspectRatioSettingsButtonHint;
     }
+
+    private boolean isInDesktopMode(int displayId) {
+        return Flags.skipCompatUiEducationInDesktopMode()
+                && mInDesktopModePredicate.test(displayId);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 98536bf..42937c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -137,6 +137,7 @@
 import dagger.Provides;
 
 import java.util.Optional;
+import java.util.function.IntPredicate;
 
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
@@ -261,6 +262,7 @@
             Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
             @NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -273,6 +275,10 @@
                     new DefaultCompatUIHandler(compatUIRepository, compatUIState,
                             componentIdGenerator, compatUIComponentFactory, mainExecutor));
         }
+        final IntPredicate inDesktopModePredicate =
+                desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
+                        modeTaskRepository.getVisibleTaskCount(displayId) > 0)
+                            .orElseGet(() -> displayId -> false);
         return Optional.of(
                 new CompatUIController(
                         context,
@@ -288,7 +294,8 @@
                         compatUIConfiguration.get(),
                         compatUIShellCommandHandler.get(),
                         accessibilityManager.get(),
-                        compatUIStatusManager));
+                        compatUIStatusManager,
+                        inDesktopModePredicate));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
index a489c4f..cc47dbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.dagger
 
+import android.os.Handler
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -24,41 +25,56 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainCoroutineDispatcher
 import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.asCoroutineDispatcher
 
-/** Providers for various WmShell-specific coroutines-related constructs. */
+/**
+ * Providers for various WmShell-specific coroutines-related constructs.
+ *
+ * Providers of [MainCoroutineDispatcher] intentionally creates the dispatcher with a [Handler]
+ * backing it instead of a [ShellExecutor] because [ShellExecutor.asCoroutineDispatcher] will
+ * create a [CoroutineDispatcher] whose [CoroutineDispatcher.isDispatchNeeded] is effectively never
+ * dispatching. This is because even if dispatched, the backing [ShellExecutor.execute] always runs
+ * the [Runnable] immediately if called from the same thread, whereas
+ * [Handler.asCoroutineDispatcher] will create a [MainCoroutineDispatcher] that correctly
+ * dispatches (queues) when [CoroutineDispatcher.isDispatchNeeded] is true using [Handler.post].
+ * For callers that do need a non-dispatching version, [MainCoroutineDispatcher.immediate] is
+ * available.
+ */
 @Module
 class WMShellCoroutinesModule {
   @Provides
   @ShellMainThread
-  fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher =
-      mainExecutor.asCoroutineDispatcher()
+  fun provideMainDispatcher(
+    @ShellMainThread mainHandler: Handler
+  ): MainCoroutineDispatcher = mainHandler.asCoroutineDispatcher()
 
   @Provides
   @ShellBackgroundThread
   fun provideBackgroundDispatcher(
-      @ShellBackgroundThread backgroundExecutor: ShellExecutor
-  ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher()
+      @ShellBackgroundThread backgroundHandler: Handler
+  ): MainCoroutineDispatcher = backgroundHandler.asCoroutineDispatcher()
 
   @Provides
   @WMSingleton
   @ShellMainThread
   fun provideApplicationScope(
-      @ShellMainThread applicationDispatcher: CoroutineDispatcher,
+      @ShellMainThread applicationDispatcher: MainCoroutineDispatcher,
   ): CoroutineScope = CoroutineScope(applicationDispatcher)
 
   @Provides
   @WMSingleton
   @ShellBackgroundThread
   fun provideBackgroundCoroutineScope(
-      @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher,
+      @ShellBackgroundThread backgroundDispatcher: MainCoroutineDispatcher,
   ): CoroutineScope = CoroutineScope(backgroundDispatcher)
 
   @Provides
   @WMSingleton
   @ShellBackgroundThread
   fun provideBackgroundCoroutineContext(
-      @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher
+      @ShellBackgroundThread backgroundDispatcher: MainCoroutineDispatcher
   ): CoroutineContext = backgroundDispatcher + SupervisorJob()
 }
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 4db8a82..46cb6ec 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
@@ -59,6 +59,7 @@
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
+import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
 import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
@@ -237,7 +238,8 @@
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
             MultiInstanceHelper multiInstanceHelper,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return new DesktopModeWindowDecorViewModel(
                     context,
@@ -259,7 +261,8 @@
                     interactionJankMonitor,
                     genericLinksParser,
                     multiInstanceHelper,
-                    desktopTasksLimiter);
+                    desktopTasksLimiter,
+                    desktopActivityOrientationHandler);
         }
         return new CaptionWindowDecorViewModel(
                 context,
@@ -677,6 +680,24 @@
 
     @WMSingleton
     @Provides
+    static Optional<DesktopActivityOrientationChangeHandler> provideActivityOrientationHandler(
+            Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            TaskStackListenerImpl taskStackListener,
+            ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
+            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
+    ) {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
+            return Optional.of(new DesktopActivityOrientationChangeHandler(
+                    context, shellInit, shellTaskOrganizer, taskStackListener,
+                    toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
+        }
+        return Optional.empty();
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
             Context context,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 51ce2c6..3464fef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -42,6 +42,7 @@
 import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipMotionHelper;
 import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTaskListener;
 import com.android.wm.shell.pip2.phone.PipTouchHandler;
 import com.android.wm.shell.pip2.phone.PipTransition;
 import com.android.wm.shell.pip2.phone.PipTransitionState;
@@ -73,12 +74,13 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             Optional<PipController> pipController,
             PipTouchHandler pipTouchHandler,
+            PipTaskListener pipTaskListener,
             @NonNull PipScheduler pipScheduler,
             @NonNull PipTransitionState pipStackListenerController,
             @NonNull PipUiStateChangeController pipUiStateChangeController) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
-                pipStackListenerController, pipUiStateChangeController);
+                pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
+                pipScheduler, pipStackListenerController, pipUiStateChangeController);
     }
 
     @WMSingleton
@@ -123,9 +125,11 @@
     @Provides
     static PipScheduler providePipScheduler(Context context,
             PipBoundsState pipBoundsState,
+            PhonePipMenuController pipMenuController,
             @ShellMainThread ShellExecutor mainExecutor,
             PipTransitionState pipTransitionState) {
-        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
+        return new PipScheduler(context, pipBoundsState, pipMenuController,
+                mainExecutor, pipTransitionState);
     }
 
     @WMSingleton
@@ -190,4 +194,17 @@
             PipTransitionState pipTransitionState) {
         return new PipUiStateChangeController(pipTransitionState);
     }
+
+    @WMSingleton
+    @Provides
+    static PipTaskListener providePipTaskListener(Context context,
+            ShellTaskOrganizer shellTaskOrganizer,
+            PipTransitionState pipTransitionState,
+            PipScheduler pipScheduler,
+            PipBoundsState pipBoundsState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
+                pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
new file mode 100644
index 0000000..59e0068
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.ScreenOrientation
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import android.util.Size
+import android.window.WindowContainerTransaction
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.TaskStackListenerCallback
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+
+/** Handles task resizing to respect orientation change of non-resizeable activities in desktop. */
+class DesktopActivityOrientationChangeHandler(
+    context: Context,
+    shellInit: ShellInit,
+    private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val taskStackListener: TaskStackListenerImpl,
+    private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
+    private val taskRepository: DesktopModeTaskRepository,
+) {
+
+    init {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
+            shellInit.addInitCallback({ onInit() }, this)
+        }
+    }
+
+    private fun onInit() {
+        taskStackListener.addListener(object : TaskStackListenerCallback {
+            override fun onActivityRequestedOrientationChanged(
+                taskId: Int,
+                @ScreenOrientation requestedOrientation: Int
+            ) {
+                // Handle requested screen orientation changes at runtime.
+                handleActivityOrientationChange(taskId, requestedOrientation)
+            }
+        })
+    }
+
+    /**
+     * Triggered with onTaskInfoChanged to handle:
+     * * New activity launching from same task with different orientation
+     * * Top activity closing in same task with different orientation to previous activity
+     */
+    fun handleActivityOrientationChange(oldTask: RunningTaskInfo, newTask: RunningTaskInfo) {
+        val newTopActivityInfo = newTask.topActivityInfo ?: return
+        val oldTopActivityInfo = oldTask.topActivityInfo ?: return
+        // Check if screen orientation is different from old task info so there is no duplicated
+        // calls to handle runtime requested orientation changes.
+        if (oldTopActivityInfo.screenOrientation != newTopActivityInfo.screenOrientation) {
+            handleActivityOrientationChange(newTask.taskId, newTopActivityInfo.screenOrientation)
+        }
+    }
+
+    private fun handleActivityOrientationChange(
+        taskId: Int,
+        @ScreenOrientation requestedOrientation: Int
+    ) {
+        if (!Flags.respectOrientationChangeForUnresizeable()) return
+        val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return
+        if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return
+
+        val taskBounds = task.configuration.windowConfiguration.bounds
+        val taskHeight = taskBounds.height()
+        val taskWidth = taskBounds.width()
+        if (taskWidth == taskHeight) return
+        val orientation =
+            if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT
+
+        // Non-resizeable activity requested opposite orientation.
+        if (orientation == ORIENTATION_PORTRAIT
+                && ActivityInfo.isFixedOrientationLandscape(requestedOrientation)
+            || orientation == ORIENTATION_LANDSCAPE
+                && ActivityInfo.isFixedOrientationPortrait(requestedOrientation)) {
+
+            val finalSize = Size(taskHeight, taskWidth)
+            // Use the center x as the resizing anchor point.
+            val left = taskBounds.centerX() - finalSize.width / 2
+            val right = left + finalSize.width
+            val finalBounds = Rect(left, taskBounds.top, right, taskBounds.top + finalSize.height)
+
+            val wct = WindowContainerTransaction().setBounds(task.token, finalBounds)
+            resizeHandler.startTransition(wct)
+        }
+    }
+
+    private fun isDesktopModeShowing(displayId: Int): Boolean =
+        taskRepository.getVisibleTaskCount(displayId) > 0
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 336e5e3..0637474 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.os.IBinder
+import android.os.SystemProperties
 import android.os.Trace
 import android.util.SparseArray
 import android.view.SurfaceControl
@@ -52,8 +53,6 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 
-const val VISIBLE_TASKS_COUNTER_NAME = "DESKTOP_MODE_VISIBLE_TASKS"
-
 /**
  * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
  * appropriate desktop mode session log events. This observes transitions related to desktop mode
@@ -307,6 +306,8 @@
                         VISIBLE_TASKS_COUNTER_NAME,
                         postTransitionVisibleFreeformTasks.size().toLong()
                     )
+                    SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+                        postTransitionVisibleFreeformTasks.size().toString())
                 }
                 // old tasks that were resized or repositioned
                 // TODO(b/347935387): Log changes only once they are stable.
@@ -326,6 +327,8 @@
                     VISIBLE_TASKS_COUNTER_NAME,
                     postTransitionVisibleFreeformTasks.size().toLong()
                 )
+                SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+                    postTransitionVisibleFreeformTasks.size().toString())
             }
         }
     }
@@ -431,4 +434,12 @@
         return this.type == WindowManager.TRANSIT_TO_FRONT &&
             this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
     }
+
+    companion object {
+        @VisibleForTesting
+        const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks"
+        @VisibleForTesting
+        const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY =
+            "debug.tracing." + VISIBLE_TASKS_COUNTER_NAME
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index b68b436..c8ffe28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -171,6 +171,18 @@
         minOf(appBounds.height(), appBounds.width()).toFloat()
 }
 
+/** Returns true if task's width or height is maximized else returns false. */
+fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
+    return taskBounds.width() == stableBounds.width() ||
+            taskBounds.height() == stableBounds.height()
+}
+
+/** Returns true if task bound is equal to stable bounds else returns false. */
+fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
+    return taskBounds.width() == stableBounds.width() &&
+            taskBounds.height() == stableBounds.height()
+}
+
 /**
  * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
  * scale of the screen bounds.
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 2852631..90f8276 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
@@ -169,6 +169,9 @@
             }
         }
 
+    @VisibleForTesting
+    var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
+
     /** Task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId
         get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -610,13 +613,10 @@
         val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
         val destinationBounds = Rect()
 
-        val isMaximized = if (taskInfo.isResizeable) {
-            currentTaskBounds == stableBounds
-        } else {
-            currentTaskBounds.width() == stableBounds.width()
-                    || currentTaskBounds.height() == stableBounds.height()
-        }
-
+        val isMaximized = isTaskMaximized(taskInfo, stableBounds)
+        // If the task is currently maximized, we will toggle it not to be and vice versa. This is
+        // helpful to eliminate the current task from logic to calculate taskbar corner rounding.
+        val willMaximize = !isMaximized
         if (isMaximized) {
             // The desktop task is at the maximized width and/or height of the stable bounds.
             // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
@@ -651,6 +651,18 @@
             }
         }
 
+
+
+        val shouldRestoreToSnap =
+            isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds)
+
+        logD("willMaximize = %s", willMaximize)
+        logD("shouldRestoreToSnap = %s", shouldRestoreToSnap)
+
+        val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap ||
+                doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId)
+
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
@@ -659,6 +671,65 @@
         }
     }
 
+    private fun isTaskMaximized(
+        taskInfo: RunningTaskInfo,
+        stableBounds: Rect
+    ): Boolean {
+        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+
+        return if (taskInfo.isResizeable) {
+            isTaskBoundsEqual(currentTaskBounds, stableBounds)
+        } else {
+            isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+        }
+    }
+
+    private fun isMaximizedToStableBoundsEdges(
+        taskInfo: RunningTaskInfo,
+        stableBounds: Rect
+    ): Boolean {
+        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+        return isTaskBoundsEqual(currentTaskBounds, stableBounds)
+    }
+
+    /** Returns if current task bound is snapped to half screen */
+    private fun isTaskSnappedToHalfScreen(
+        taskInfo: RunningTaskInfo,
+        taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds
+    ): Boolean =
+        getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds ||
+                getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
+
+    @VisibleForTesting
+    fun doesAnyTaskRequireTaskbarRounding(
+        displayId: Int,
+        excludeTaskId: Int? = null,
+    ): Boolean {
+        val doesAnyTaskRequireTaskbarRounding =
+            taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
+                // exclude current task since maximize/restore transition has not taken place yet.
+                .filterNot { taskId -> taskId == excludeTaskId }
+                .any { taskId ->
+                    val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)!!
+                    val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
+                    val stableBounds = Rect().apply { displayLayout?.getStableBounds(this) }
+                    logD("taskInfo = %s", taskInfo)
+                    logD(
+                        "isTaskSnappedToHalfScreen(taskInfo) = %s",
+                        isTaskSnappedToHalfScreen(taskInfo)
+                    )
+                    logD(
+                        "isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s",
+                        isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+                    )
+                    isTaskSnappedToHalfScreen(taskInfo)
+                            || isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+                }
+
+        logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding)
+        return doesAnyTaskRequireTaskbarRounding
+    }
+
     /**
      * Quick-resize to the right or left half of the stable bounds.
      *
@@ -673,9 +744,9 @@
         position: SnapPosition
     ) {
         val destinationBounds = getSnapBounds(taskInfo, position)
-
         if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
 
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)
@@ -806,6 +877,10 @@
             .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
             .reversed() // Start from the back so the front task is brought forward last
             .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
+
+        taskbarDesktopTaskListener?.
+            onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
+
         return taskToMinimize
     }
 
@@ -1156,6 +1231,12 @@
         ) {
             wct.removeTask(task.token)
         }
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+            doesAnyTaskRequireTaskbarRounding(
+                task.displayId,
+                task.id
+            )
+        )
         return if (wct.isEmpty) null else wct
     }
 
@@ -1450,6 +1531,8 @@
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
+        taskbarDesktopTaskListener
+            ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
     }
 
     /**
@@ -1686,17 +1769,39 @@
                 }
             }
 
+        private val mTaskbarDesktopTaskListener: TaskbarDesktopTaskListener =
+                object : TaskbarDesktopTaskListener {
+                    override fun onTaskbarCornerRoundingUpdate(
+                        hasTasksRequiringTaskbarRounding: Boolean) {
+                        ProtoLog.v(
+                                WM_SHELL_DESKTOP_MODE,
+                                "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
+                                        "doesAnyTaskRequireTaskbarRounding=%s",
+                                hasTasksRequiringTaskbarRounding
+                        )
+
+                        remoteListener.call { l ->
+                            l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
+                        }
+                    }
+                }
+
         init {
             remoteListener =
                 SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
                     controller,
                     { c ->
-                        c.taskRepository.addVisibleTasksListener(
-                            listener,
-                            c.mainExecutor
-                        )
+                        run {
+                            c.taskRepository.addVisibleTasksListener(listener, c.mainExecutor)
+                            c.taskbarDesktopTaskListener = mTaskbarDesktopTaskListener
+                        }
                     },
-                    { c -> c.taskRepository.removeVisibleTasksListener(listener) }
+                    { c ->
+                        run {
+                            c.taskRepository.removeVisibleTasksListener(listener)
+                            c.taskbarDesktopTaskListener = null
+                        }
+                    }
                 )
         }
 
@@ -1779,6 +1884,16 @@
         private const val TAG = "DesktopTasksController"
     }
 
+    /** Defines interface for classes that can listen to changes for task resize. */
+    // TODO(b/343931111): Migrate to using TransitionObservers when ready
+    interface TaskbarDesktopTaskListener {
+        /**
+         * [hasTasksRequiringTaskbarRounding] is true when a task is either maximized or snapped
+         * left/right and rounded corners are enabled.
+         */
+        fun onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding: Boolean)
+    }
+
     /** The positions on a screen that a task can snap to. */
     enum class SnapPosition {
         RIGHT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 1a103d3..d72ec90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -22,13 +22,15 @@
 import android.os.Bundle
 import android.os.IBinder
 import android.os.SystemClock
+import android.os.SystemProperties
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CLOSE
 import android.window.TransitionInfo
 import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
-import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.dynamicanimation.animation.SpringForce
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
 import com.android.internal.jank.InteractionJankMonitor
@@ -893,13 +895,10 @@
     ) {
 
     private val positionSpringConfig =
-        PhysicsAnimator.SpringConfig(
-            SpringForce.STIFFNESS_LOW,
-            SpringForce.DAMPING_RATIO_LOW_BOUNCY
-        )
+        PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO)
 
     private val sizeSpringConfig =
-        PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+        PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO)
 
     /**
      * @return layers in order:
@@ -929,7 +928,7 @@
         finishTransaction.hide(homeLeash)
         // Setup freeform tasks before animation
         state.freeformTaskChanges.forEach { change ->
-            val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+            val startScale = FREEFORM_TASKS_INITIAL_SCALE
             val startX =
                 change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
             val startY =
@@ -994,9 +993,22 @@
                     (animBounds.width() - startBounds.width()).toFloat() /
                         (endBounds.width() - startBounds.width())
                 val animScale = startScale + animFraction * (1 - startScale)
-                // Freeform animation starts 50% in the animation
-                val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
-                val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+                // Freeform animation starts with freeform animation offset relative to the commit
+                // animation and plays until the commit animation ends. For instance:
+                // - if the freeform animation offset is `0.0` the freeform tasks animate alongside
+                // - if the freeform animation offset is `0.6` the freeform tasks will
+                //   start animating at 60% fraction of the commit animation and will complete when
+                //   the commit animation fraction is 100%.
+                // - if the freeform animation offset is `1.0` then freeform tasks will appear
+                //   without animation after commit animation finishes.
+                val freeformAnimFraction =
+                    if (FREEFORM_TASKS_ANIM_OFFSET != 1f) {
+                        max(animFraction - FREEFORM_TASKS_ANIM_OFFSET, 0f) /
+                            (1f - FREEFORM_TASKS_ANIM_OFFSET)
+                    } else {
+                        0f
+                    }
+                val freeformStartScale = FREEFORM_TASKS_INITIAL_SCALE
                 val freeformAnimScale =
                     freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
                 tx.apply {
@@ -1032,10 +1044,53 @@
     }
 
     companion object {
+        /** The freeform tasks initial scale when committing the drag-to-desktop gesture. */
+        private val FREEFORM_TASKS_INITIAL_SCALE =
+            propertyValue("freeform_tasks_initial_scale", scale = 100f, default = 0.9f)
+
+        /** The freeform tasks animation offset relative to the whole animation duration. */
+        private val FREEFORM_TASKS_ANIM_OFFSET =
+            propertyValue("freeform_tasks_anim_offset", scale = 100f, default = 0.5f)
+
+        /** The spring force stiffness used to place the window into the final position. */
+        private val POSITION_SPRING_STIFFNESS =
+            propertyValue("position_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+        /** The spring force damping ratio used to place the window into the final position. */
+        private val POSITION_SPRING_DAMPING_RATIO =
+            propertyValue(
+                "position_damping_ratio",
+                scale = 100f,
+                default = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+            )
+
+        /** The spring force stiffness used to resize the window into the final bounds. */
+        private val SIZE_SPRING_STIFFNESS =
+            propertyValue("size_stiffness", default = SpringForce.STIFFNESS_LOW)
+
+        /** The spring force damping ratio used to resize the window into the final bounds. */
+        private val SIZE_SPRING_DAMPING_RATIO =
+            propertyValue(
+                "size_damping_ratio",
+                scale = 100f,
+                default = SpringForce.DAMPING_RATIO_NO_BOUNCY
+            )
+
+        /** Drag to desktop transition system properties group. */
+        @VisibleForTesting
+        const val SYSTEM_PROPERTIES_GROUP = "persist.wm.debug.desktop_transitions.drag_to_desktop"
+
         /**
-         * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
-         * gesture.
+         * Drag to desktop transition system property value with [name].
+         *
+         * @param scale an optional scale to apply to the value read from the system property.
+         * @param default a default value to return if the system property isn't set.
          */
-        private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
+        @VisibleForTesting
+        fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float =
+            SystemProperties.getInt(
+                /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name",
+                /* def= */ (default * scale).toInt()
+            ) / scale
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 8ebdfdc..c2acb87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -27,4 +27,10 @@
 
     /** @deprecated this is no longer supported. */
     oneway void onStashedChanged(int displayId, boolean stashed);
+
+    /**
+     * Shows taskbar corner radius when running desktop tasks are updated if
+     * [hasTasksRequiringTaskbarRounding] is true.
+     */
+    oneway void onTaskbarCornerRoundingUpdate(boolean hasTasksRequiringTaskbarRounding);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 84f6af41..72d1a76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -27,10 +27,13 @@
   traces in Winscope)
 
 ### Kotlin
+Kotlin protologging is supported but not as optimized as in Java.
 
-Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
-class which has a similar API to the Java ProtoLog class.
+The Protolog tool does not yet have support for Kotlin code ([b/168581922](https://b.corp.google.com/issues/168581922)).
+
+What this implies is that ProtoLogs are not pre-processed to extract the static strings out when used in Kotlin. So,
+there is no memory gain when using ProtoLogging in Kotlin. The logs will still be traced to Perfetto, but with a subtly
+worse performance due to the additional string interning that needs to be done at run time instead of at build time.
 
 ### Enabling ProtoLog command line logging
 Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 88f9e4c..d565776 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -134,9 +134,10 @@
             Rect baseBounds, Rect targetBounds, float degrees) {
         Matrix transformTensor = new Matrix();
         final float[] mMatrixTmp = new float[9];
-        final float scale = (float) targetBounds.width() / baseBounds.width();
+        final float scaleX = (float) targetBounds.width() / baseBounds.width();
+        final float scaleY = (float) targetBounds.height() / baseBounds.height();
 
-        transformTensor.setScale(scale, scale);
+        transformTensor.setScale(scaleX, scaleY);
         transformTensor.postTranslate(targetBounds.left, targetBounds.top);
         transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
 
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 218d456..0324fdb 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
@@ -56,7 +56,6 @@
 import kotlin.jvm.functions.Function0;
 
 import java.util.Optional;
-import java.util.function.Consumer;
 
 /**
  * A helper to animate and manipulate the PiP.
@@ -134,18 +133,6 @@
     private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
             new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
 
-    @Nullable private Runnable mUpdateMovementBoundsRunnable;
-
-    private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
-        if (mPipBoundsState.getBounds().equals(newBounds)) {
-            return;
-        }
-
-        mMenuController.updateMenuLayout(newBounds);
-        mPipBoundsState.setBounds(newBounds);
-        maybeUpdateMovementBounds();
-    };
-
     /**
      * Whether we're springing to the touch event location (vs. moving it to that position
      * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
@@ -683,16 +670,6 @@
         cleanUpHighPerfSessionMaybe();
     }
 
-    void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
-        mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
-    }
-
-    private void maybeUpdateMovementBounds() {
-        if (mUpdateMovementBoundsRunnable != null)  {
-            mUpdateMovementBoundsRunnable.run();
-        }
-    }
-
     /**
      * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
      * we return these bounds from
@@ -720,7 +697,7 @@
     /**
      * Directly resizes the PiP to the given {@param bounds}.
      */
-    private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+    void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
         if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
             // Do not carry out any resizing if we are dragging or physics animator is running.
             return;
@@ -813,7 +790,7 @@
         cleanUpHighPerfSessionMaybe();
 
         // Signal that the transition is done - should update transition state by default.
-        mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
+        mPipScheduler.scheduleFinishResizePip(destinationBounds, false /* configAtEnd */);
     }
 
     private void startResizeAnimation(SurfaceControl.Transaction startTx,
@@ -829,8 +806,6 @@
                 startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
                 destinationBounds, duration, 0f /* angle */);
         animator.setAnimationEndCallback(() -> {
-            mUpdateBoundsCallback.accept(destinationBounds);
-
             // In case an ongoing drag/fling was present before a deterministic resize transition
             // kicked in, we need to update the update bounds properly before cleaning in-motion
             // state.
@@ -839,7 +814,7 @@
 
             cleanUpHighPerfSessionMaybe();
             // Signal that we are done with resize transition
-            mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+            mPipScheduler.scheduleFinishResizePip(destinationBounds, true /* configAtEnd */);
         });
         animator.start();
     }
@@ -849,7 +824,6 @@
             // The physics animation ended, though we may not necessarily be done animating, such as
             // when we're still dragging after moving out of the magnetic target. Only set the final
             // bounds state and clear motion bounds completely if the whole animation is over.
-            mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
             mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
         }
         mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index d28204a..f5ef64d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -50,7 +50,6 @@
 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
 
 import java.io.PrintWriter;
-import java.util.function.Consumer;
 
 /**
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -86,8 +85,6 @@
     private final Rect mUserResizeBounds = new Rect();
     private final Rect mDownBounds = new Rect();
     private final Rect mStartBoundsAfterRelease = new Rect();
-    private final Runnable mUpdateMovementBoundsRunnable;
-    private final Consumer<Rect> mUpdateResizeBoundsCallback;
 
     private float mTouchSlop;
 
@@ -121,7 +118,6 @@
             PipTouchState pipTouchState,
             PipScheduler pipScheduler,
             PipTransitionState pipTransitionState,
-            Runnable updateMovementBoundsRunnable,
             PipUiEventLogger pipUiEventLogger,
             PhonePipMenuController menuActivityController,
             ShellExecutor mainExecutor,
@@ -138,18 +134,9 @@
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this);
 
-        mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
         mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
-
-        mUpdateResizeBoundsCallback = (rect) -> {
-            mUserResizeBounds.set(rect);
-            // mMotionHelper.synchronizePinnedStackBounds();
-            mPipBoundsState.setBounds(rect);
-            mUpdateMovementBoundsRunnable.run();
-            resetState();
-        };
     }
 
     void init() {
@@ -563,11 +550,13 @@
                         mLastResizeBounds, duration, mAngle);
                 animator.setAnimationEndCallback(() -> {
                     // All motion operations have actually finished, so make bounds cache updates.
-                    mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
+                    mUserResizeBounds.set(mLastResizeBounds);
+                    resetState();
                     cleanUpHighPerfSessionMaybe();
 
                     // Signal that we are done with resize transition
-                    mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
+                    mPipScheduler.scheduleFinishResizePip(
+                            mLastResizeBounds, true /* configAtEnd */);
                 });
                 animator.start();
                 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 ac670cf..f4defdc 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
@@ -52,11 +52,14 @@
 
     private final Context mContext;
     private final PipBoundsState mPipBoundsState;
+    private final PhonePipMenuController mPipMenuController;
     private final ShellExecutor mMainExecutor;
     private final PipTransitionState mPipTransitionState;
     private PipSchedulerReceiver mSchedulerReceiver;
     private PipTransitionController mPipTransitionController;
 
+    @Nullable private Runnable mUpdateMovementBoundsRunnable;
+
     /**
      * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
      * This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -94,10 +97,12 @@
 
     public PipScheduler(Context context,
             PipBoundsState pipBoundsState,
+            PhonePipMenuController pipMenuController,
             ShellExecutor mainExecutor,
             PipTransitionState pipTransitionState) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
+        mPipMenuController = pipMenuController;
         mMainExecutor = mainExecutor;
         mPipTransitionState = pipTransitionState;
 
@@ -189,9 +194,13 @@
      * Signals to Core to finish the PiP resize transition.
      * Note that we do not allow any actual WM Core changes at this point.
      *
+     * @param toBounds destination bounds used only for internal state updates - not sent to Core.
      * @param configAtEnd true if we are waiting for config updates at the end of the transition.
      */
-    public void scheduleFinishResizePip(boolean configAtEnd) {
+    public void scheduleFinishResizePip(Rect toBounds, boolean configAtEnd) {
+        // Make updates to the internal state to reflect new bounds
+        onFinishingPipResize(toBounds);
+
         SurfaceControl.Transaction tx = null;
         if (configAtEnd) {
             tx = new SurfaceControl.Transaction();
@@ -238,4 +247,23 @@
         tx.setMatrix(leash, transformTensor, mMatrixTmp);
         tx.apply();
     }
+
+    void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+        mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+    }
+
+    private void maybeUpdateMovementBounds() {
+        if (mUpdateMovementBoundsRunnable != null)  {
+            mUpdateMovementBoundsRunnable.run();
+        }
+    }
+
+    private void onFinishingPipResize(Rect newBounds) {
+        if (mPipBoundsState.getBounds().equals(newBounds)) {
+            return;
+        }
+        mPipBoundsState.setBounds(newBounds);
+        mPipMenuController.updateMenuLayout(newBounds);
+        maybeUpdateMovementBounds();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
new file mode 100644
index 0000000..7f16880
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -0,0 +1,161 @@
+/*
+ * 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.pip2.phone;
+
+import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION;
+
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+
+/**
+ * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
+ * Transitions framework directly.
+ * Hence, it's the intention to keep the usage of this class for a very limited set of cases.
+ */
+public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
+        PipTransitionState.PipTransitionStateChangedListener {
+    private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
+    private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+
+    private final Context mContext;
+    private final PipTransitionState mPipTransitionState;
+    private final PipScheduler mPipScheduler;
+    private final PipBoundsState mPipBoundsState;
+    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private final ShellExecutor mMainExecutor;
+    private final PictureInPictureParams mPictureInPictureParams =
+            new PictureInPictureParams.Builder().build();
+
+    private boolean mWaitingForAspectRatioChange = false;
+
+    public PipTaskListener(Context context,
+            ShellTaskOrganizer shellTaskOrganizer,
+            PipTransitionState pipTransitionState,
+            PipScheduler pipScheduler,
+            PipBoundsState pipBoundsState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        mContext = context;
+        mPipTransitionState = pipTransitionState;
+        mPipScheduler = pipScheduler;
+        mPipBoundsState = pipBoundsState;
+        mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mMainExecutor = mainExecutor;
+
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            mMainExecutor.execute(() -> {
+                shellTaskOrganizer.addListenerForType(this,
+                        ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
+            });
+        }
+    }
+
+    void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
+        if (mPictureInPictureParams.equals(params)) {
+            return;
+        }
+        mPictureInPictureParams.copyOnlySet(params != null ? params
+                : new PictureInPictureParams.Builder().build());
+    }
+
+    @Override
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        PictureInPictureParams params = taskInfo.pictureInPictureParams;
+        if (mPictureInPictureParams.equals(params)) {
+            return;
+        }
+        setPictureInPictureParams(params);
+        float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
+        if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
+            mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+                onAspectRatioChanged(newAspectRatio);
+            });
+        }
+    }
+
+    private void onAspectRatioChanged(float ratio) {
+        mPipBoundsState.setAspectRatio(ratio);
+
+        final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+                mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
+        // Avoid scheduling a resize transition if destination bounds are unchanged, otherise
+        // we could end up with a no-op transition.
+        if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+            Bundle extra = new Bundle();
+            extra.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+            mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+        }
+    }
+
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                mWaitingForAspectRatioChange = extra.getBoolean(ANIMATING_ASPECT_RATIO_CHANGE);
+                if (!mWaitingForAspectRatioChange) break;
+
+                mPipScheduler.scheduleAnimateResizePip(
+                        mPipBoundsAlgorithm.getAdjustedDestinationBounds(
+                                mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio()),
+                        false /* configAtEnd */, ASPECT_RATIO_CHANGE_DURATION);
+                break;
+            case PipTransitionState.CHANGING_PIP_BOUNDS:
+                final SurfaceControl.Transaction startTx = extra.getParcelable(
+                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                final SurfaceControl.Transaction finishTx = extra.getParcelable(
+                        PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
+                final Rect destinationBounds = extra.getParcelable(
+                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+                final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
+                        PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
+
+                Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+                        "Leash is null for bounds transition.");
+
+                if (mWaitingForAspectRatioChange) {
+                    PipResizeAnimator animator = new PipResizeAnimator(mContext,
+                            mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+                            destinationBounds,
+                            mPipBoundsState.getBounds(), destinationBounds, duration,
+                            0f /* delta */);
+                    animator.setAnimationEndCallback(() -> {
+                        mPipScheduler.scheduleFinishResizePip(
+                                destinationBounds, false /* configAtEnd */);
+                    });
+                    animator.start();
+                }
+                break;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index d75fa00..029f001 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -206,7 +206,7 @@
         mMenuController.addListener(new PipMenuListener());
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = pipMotionHelper;
-        mMotionHelper.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
+        mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
         mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
                 mMotionHelper, mainExecutor);
         mTouchState = new PipTouchState(ViewConfiguration.get(context),
@@ -219,8 +219,8 @@
                 menuController::hideMenu,
                 mainExecutor);
         mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
-                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
-                this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
+                menuController, mainExecutor,
                 mPipPerfHintController);
         mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
 
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 c6b1a72..44baabd 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
@@ -87,6 +87,7 @@
     //
 
     private final Context mContext;
+    private final PipTaskListener mPipTaskListener;
     private final PipScheduler mPipScheduler;
     private final PipTransitionState mPipTransitionState;
 
@@ -118,6 +119,7 @@
             PipBoundsState pipBoundsState,
             PipMenuController pipMenuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipTaskListener pipTaskListener,
             PipScheduler pipScheduler,
             PipTransitionState pipTransitionState,
             PipUiStateChangeController pipUiStateChangeController) {
@@ -125,6 +127,7 @@
                 pipBoundsAlgorithm);
 
         mContext = context;
+        mPipTaskListener = pipTaskListener;
         mPipScheduler = pipScheduler;
         mPipScheduler.setPipTransitionController(this);
         mPipTransitionState = pipTransitionState;
@@ -510,6 +513,7 @@
         // cache the original task token to check for multi-activity case later
         final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
         PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+        mPipTaskListener.setPictureInPictureParams(pipParams);
         mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
                 pipParams, mPipBoundsAlgorithm);
 
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 95f864a..8921ceb 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
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -684,6 +685,7 @@
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
+        prepareTasksForSplitScreen(new int[] {taskId1, taskId2}, wct);
         wct.startTask(taskId1, options1);
 
         startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId);
@@ -714,6 +716,7 @@
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
         wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+        prepareTasksForSplitScreen(new int[] {taskId}, wct);
 
         startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
     }
@@ -757,11 +760,30 @@
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
         wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+        prepareTasksForSplitScreen(new int[] {taskId}, wct);
 
         startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
     }
 
     /**
+     * Prepares the tasks whose IDs are provided in `taskIds` for split screen by clearing their
+     * bounds and windowing mode so that they can inherit the bounds and the windowing mode of
+     * their root stages.
+     *
+     * @param taskIds an array of task IDs whose bounds will be cleared.
+     * @param wct     transaction to clear the bounds on the tasks.
+     */
+    private void prepareTasksForSplitScreen(int[] taskIds, WindowContainerTransaction wct) {
+        for (int taskId : taskIds) {
+            ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+            if (task != null) {
+                wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
+                        .setBounds(task.token, null);
+            }
+        }
+    }
+
+    /**
      * Starts with the second task to a split pair in one transition.
      *
      * @param wct        transaction to start the first task
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 4fc6c44..9b0fb20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,8 +18,8 @@
 
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
+import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -473,7 +473,7 @@
                                 change.getLeash(),
                                 startTransaction);
                     } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
-                                && TransitionUtil.isClosingType(mode)) {
+                            && TransitionUtil.isClosingType(mode)) {
                         // If there is a closing translucent task in an OPENING transition, we will
                         // actually select a CLOSING animation, so move the closing task into
                         // the animating part of the z-order.
@@ -767,12 +767,12 @@
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
-            a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
+            a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(options.getUserId());
         } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
             if (isOpeningType) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
+                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter, options.getUserId());
             } else {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
+                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter, options.getUserId());
             }
         } else if (changeMode == TRANSIT_CHANGE) {
             // In the absence of a specific adapter, we just want to keep everything stationary.
@@ -783,9 +783,9 @@
         } else if (overrideType == ANIM_CUSTOM
                 && (!isTask || options.getOverrideTaskTransition())) {
             a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
-                    ? options.getEnterResId() : options.getExitResId());
+                    ? options.getEnterResId() : options.getExitResId(), options.getUserId());
         } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
-            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
+            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(options.getUserId());
         } else if (overrideType == ANIM_CLIP_REVEAL) {
             a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
                     endBounds, endBounds, options.getTransitionBounds());
@@ -905,9 +905,9 @@
         final Rect bounds = change.getEndAbsBounds();
         // Show the right drawable depending on the user we're transitioning to.
         final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
-                        ? mContext.getDrawable(R.drawable.ic_account_circle)
-                        : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
-                                ? mEnterpriseThumbnailDrawable : null;
+                ? mContext.getDrawable(R.drawable.ic_account_circle)
+                : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+                        ? mEnterpriseThumbnailDrawable : null;
         if (thumbnailDrawable == null) {
             return;
         }
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 1f95667..ac354593 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
@@ -97,6 +97,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -166,6 +167,8 @@
     private TaskOperations mTaskOperations;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
     private final Transitions mTransitions;
+    private final Optional<DesktopActivityOrientationChangeHandler>
+            mActivityOrientationChangeHandler;
 
     private SplitScreenController mSplitScreenController;
 
@@ -215,7 +218,8 @@
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
             MultiInstanceHelper multiInstanceHelper,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler
     ) {
         this(
                 context,
@@ -241,7 +245,8 @@
                 rootTaskDisplayAreaOrganizer,
                 new SparseArray<>(),
                 interactionJankMonitor,
-                desktopTasksLimiter);
+                desktopTasksLimiter,
+                activityOrientationChangeHandler);
     }
 
     @VisibleForTesting
@@ -269,7 +274,8 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
             InteractionJankMonitor interactionJankMonitor,
-            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -297,6 +303,7 @@
                 com.android.internal.R.string.config_systemUi);
         mInteractionJankMonitor = interactionJankMonitor;
         mDesktopTasksLimiter = desktopTasksLimiter;
+        mActivityOrientationChangeHandler = activityOrientationChangeHandler;
         mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
             DesktopModeWindowDecoration decoration;
             RunningTaskInfo taskInfo;
@@ -388,6 +395,8 @@
             incrementEventReceiverTasks(taskInfo.displayId);
         }
         decoration.relayout(taskInfo);
+        mActivityOrientationChangeHandler.ifPresent(handler ->
+                handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
     }
 
     @Override
@@ -496,16 +505,16 @@
         if (decoration == null) {
             return;
         }
-        openInBrowser(uri);
+        openInBrowser(uri, decoration.getUser());
         decoration.closeHandleMenu();
         decoration.closeMaximizeMenu();
     }
 
-    private void openInBrowser(Uri uri) {
+    private void openInBrowser(Uri uri, @NonNull UserHandle userHandle) {
         final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER)
                 .setData(uri)
                 .addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
+        mContext.startActivityAsUser(intent, userHandle);
     }
 
     private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
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 2bec3fa..81251b8 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
@@ -54,6 +54,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.Size;
 import android.util.Slog;
 import android.view.Choreographer;
@@ -480,6 +481,10 @@
         return mGenericLink;
     }
 
+    UserHandle getUser() {
+        return mUserContext.getUser();
+    }
+
     private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
         if (!isDragResizable(mTaskInfo)) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index deef378..9c73e4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -121,8 +121,14 @@
 
     /** Closes the maximize window and releases its view. */
     fun close() {
-        maximizeMenuView?.animateCloseMenu {
-            maximizeMenu?.releaseView()
+        val view = maximizeMenuView
+        val menu = maximizeMenu
+        if (view == null) {
+            menu?.releaseView()
+        } else {
+            view.animateCloseMenu {
+                menu?.releaseView()
+            }
         }
         maximizeMenu = null
         maximizeMenuView = null
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 7f2c1a8..4a884eb5 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
@@ -104,9 +104,6 @@
                 wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
                 mTaskOrganizer.applyTransaction(wct);
             }
-        } else {
-            mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
-                    mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
@@ -133,6 +130,9 @@
                 mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
             }
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
+            // Begin window drag CUJ instrumentation only when drag position moves.
+            mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
+                    mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
                     mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 3fb67cd..507ad64 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -17,11 +17,17 @@
 package com.android.wm.shell.flicker
 
 import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
 import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
 import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
+import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxBoundsInOnlyOneDimension
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayHeight
+import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayWidth
 import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
 import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
@@ -51,21 +57,21 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
                 extractor =
-                    ShellTransitionScenarioExtractor(
-                        transitionMatcher =
-                            object : ITransitionMatcher {
-                                override fun findAll(
-                                    transitions: Collection<Transition>
-                                ): Collection<Transition> {
-                                    return transitions.filter {
-                                        // TODO(351168217) Use jank CUJ to extract a longer trace
-                                        it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
-                                    }
-                                }
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            return transitions.filter {
+                                // TODO(351168217) Use jank CUJ to extract a longer trace
+                                it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
                             }
-                    ),
+                        }
+                    }
+                ),
                 assertions =
-                    AssertionTemplates.COMMON_ASSERTIONS +
+                AssertionTemplates.COMMON_ASSERTIONS +
                         listOf(
                             AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
                             AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
@@ -81,24 +87,24 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("CLOSE_APP"),
                 extractor =
-                    ShellTransitionScenarioExtractor(
-                        transitionMatcher =
-                            object : ITransitionMatcher {
-                                override fun findAll(
-                                    transitions: Collection<Transition>
-                                ): Collection<Transition> {
-                                    // In case there are multiple windows closing, filter out the
-                                    // last window closing. It should use the CLOSE_LAST_APP
-                                    // scenario below.
-                                    return transitions
-                                        .filter { it.type == TransitionType.CLOSE }
-                                        .sortedByDescending { it.id }
-                                        .drop(1)
-                                }
-                            }
-                    ),
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            // In case there are multiple windows closing, filter out the
+                            // last window closing. It should use the CLOSE_LAST_APP
+                            // scenario below.
+                            return transitions
+                                .filter { it.type == TransitionType.CLOSE }
+                                .sortedByDescending { it.id }
+                                .drop(1)
+                        }
+                    }
+                ),
                 assertions =
-                    AssertionTemplates.COMMON_ASSERTIONS +
+                AssertionTemplates.COMMON_ASSERTIONS +
                         listOf(
                             AppWindowOnTopAtStart(DESKTOP_MODE_APP),
                             AppLayerIsVisibleAtStart(DESKTOP_MODE_APP),
@@ -110,22 +116,22 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("CLOSE_LAST_APP"),
                 extractor =
-                    ShellTransitionScenarioExtractor(
-                        transitionMatcher =
-                            object : ITransitionMatcher {
-                                override fun findAll(
-                                    transitions: Collection<Transition>
-                                ): Collection<Transition> {
-                                    val lastTransition =
-                                        transitions
-                                            .filter { it.type == TransitionType.CLOSE }
-                                            .maxByOrNull { it.id }!!
-                                    return listOf(lastTransition)
-                                }
-                            }
-                    ),
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            val lastTransition =
+                                transitions
+                                    .filter { it.type == TransitionType.CLOSE }
+                                    .maxByOrNull { it.id }!!
+                            return listOf(lastTransition)
+                        }
+                    }
+                ),
                 assertions =
-                    AssertionTemplates.COMMON_ASSERTIONS +
+                AssertionTemplates.COMMON_ASSERTIONS +
                         listOf(
                             AppWindowIsInvisibleAtEnd(DESKTOP_MODE_APP),
                             LauncherWindowReplacesAppAsTopWindow(DESKTOP_MODE_APP),
@@ -138,12 +144,12 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("CORNER_RESIZE"),
                 extractor =
-                    TaggedScenarioExtractorBuilder()
-                        .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
-                        .setTransitionMatcher(
-                            TaggedCujTransitionMatcher(associatedTransitionRequired = false)
-                        )
-                        .build(),
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
                 assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
             )
 
@@ -151,18 +157,78 @@
             FlickerConfigEntry(
                 scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"),
                 extractor =
-                    TaggedScenarioExtractorBuilder()
-                        .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
-                        .setTransitionMatcher(
-                            TaggedCujTransitionMatcher(associatedTransitionRequired = false)
-                        )
-                        .build(),
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
                 assertions =
                 AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
                         listOf(AppWindowHasSizeOfAtLeast(DESKTOP_MODE_APP, 770, 700))
                             .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
+        val SNAP_RESIZE_LEFT_WITH_BUTTON =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_BUTTON"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
+                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
+        val SNAP_RESIZE_RIGHT_WITH_BUTTON =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_RIGHT_WITH_BUTTON"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
+                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
+        val SNAP_RESIZE_LEFT_WITH_DRAG =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_DRAG"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
+                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
+        val SNAP_RESIZE_RIGHT_WITH_DRAG =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_RIGHT_WITH_DRAG"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
+                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
         val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE =
             FlickerConfigEntry(
                 scenarioId = ScenarioId("SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"),
@@ -181,5 +247,42 @@
                     AppWindowReturnsToStartBoundsAndPosition(NON_RESIZABLE_APP)
                 ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
+
+        val MAXIMIZE_APP =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("MAXIMIZE_APP"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(
+                            AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+                            AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
+                            AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
+        val MAXIMIZE_APP_NON_RESIZABLE =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("MAXIMIZE_APP_NON_RESIZABLE"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions =
+                AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(
+                            AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+                            AppWindowMaintainsAspectRatioAlways(DESKTOP_MODE_APP),
+                            AppWindowHasMaxBoundsInOnlyOneDimension(DESKTOP_MODE_APP)
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt
new file mode 100644
index 0000000..2179566
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppLandscape.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppLandscape : MaximizeAppWindow(rotation = ROTATION_90) {
+    @ExpectedScenarios(["MAXIMIZE_APP"])
+    @Test
+    override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt
new file mode 100644
index 0000000..b173a60
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizableLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP_NON_RESIZABLE
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize non-resizable app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, maintaining its aspect ratio, until
+ * filling the vertical or horizontal stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppNonResizableLandscape : MaximizeAppWindow(
+    rotation = ROTATION_90,
+    isResizable = false
+) {
+    @ExpectedScenarios(["MAXIMIZE_APP_NON_RESIZABLE"])
+    @Test
+    override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP_NON_RESIZABLE)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt
new file mode 100644
index 0000000..88888ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppNonResizablePortrait.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP_NON_RESIZABLE
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize non-resizable app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, maintaining its aspect ratio, until
+ * filling the vertical or horizontal stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppNonResizablePortrait : MaximizeAppWindow(isResizable = false) {
+    @ExpectedScenarios(["MAXIMIZE_APP_NON_RESIZABLE"])
+    @Test
+    override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP_NON_RESIZABLE)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt
new file mode 100644
index 0000000..b79fd203
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppPortrait.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by pressing the maximize button on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppPortrait : MaximizeAppWindow() {
+    @ExpectedScenarios(["MAXIMIZE_APP"])
+    @Test
+    override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithButton.kt
new file mode 100644
index 0000000..b5090086
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithButton.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_LEFT_WITH_BUTTON
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window using the Snap Left button from the maximize menu.
+ *
+ * Assert that the app window fills the left half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowLeftWithButton : SnapResizeAppWindowWithButton(toLeft = true) {
+    @ExpectedScenarios(["SNAP_RESIZE_LEFT_WITH_BUTTON"])
+    @Test
+    override fun snapResizeAppWindowWithButton() = super.snapResizeAppWindowWithButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_LEFT_WITH_BUTTON)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithDrag.kt
new file mode 100644
index 0000000..a22e760
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithDrag.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_LEFT_WITH_DRAG
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window by dragging it to the left edge of the screen.
+ *
+ * Assert that the app window fills the left half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowLeftWithDrag : SnapResizeAppWindowWithDrag(toLeft = true) {
+    @ExpectedScenarios(["SNAP_RESIZE_LEFT_WITH_DRAG"])
+    @Test
+    override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_LEFT_WITH_DRAG)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithButton.kt
new file mode 100644
index 0000000..375a2b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithButton.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_RIGHT_WITH_BUTTON
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window using the Snap Right button from the maximize menu.
+ *
+ * Assert that the app window fills the right half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowRightWithButton : SnapResizeAppWindowWithButton(toLeft = false) {
+    @ExpectedScenarios(["SNAP_RESIZE_RIGHT_WITH_BUTTON"])
+    @Test
+    override fun snapResizeAppWindowWithButton() = super.snapResizeAppWindowWithButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_RIGHT_WITH_BUTTON)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithDrag.kt
new file mode 100644
index 0000000..4a9daf7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithDrag.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_RIGHT_WITH_DRAG
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window by dragging it to the right edge of the screen.
+ *
+ * Assert that the app window fills the right half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowRightWithDrag : SnapResizeAppWindowWithDrag(toLeft = false) {
+    @ExpectedScenarios(["SNAP_RESIZE_RIGHT_WITH_DRAG"])
+    @Test
+    override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_RIGHT_WITH_DRAG)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 90b9798..cbd4a52 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -181,6 +181,13 @@
         super.taskBarWindowIsAlwaysVisible()
     }
 
+    // Overridden to remove @Postsubmit annotation
+    @Test
+    @FlakyTest(bugId = 294993100)
+    override fun pipLayerHasCorrectCornersAtEnd() {
+        // No rounded corners as we go back to fullscreen in new orientation.
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index ed2a0a7..578a9b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -147,6 +147,12 @@
     @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
+    @Postsubmit
+    @Test
+    override fun pipLayerHasCorrectCornersAtEnd() {
+        flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index 8cb81b4..f57335c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -70,6 +70,11 @@
         }
     }
 
+    @Test
+    override fun pipLayerHasCorrectCornersAtEnd() {
+        // PiP might have completely faded out by this point, so corner radii not applicable.
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index 0742cf9..ce84eb6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip.common
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
 import android.tools.flicker.legacy.LegacyFlickerTest
@@ -123,6 +124,12 @@
         }
     }
 
+    @Postsubmit
+    @Test
+    override fun pipLayerHasCorrectCornersAtEnd() {
+        flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+    }
+
     /** {@inheritDoc} */
     @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 99c1ad2..bc2bfdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation
 import android.content.Intent
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
 import android.tools.flicker.legacy.FlickerBuilder
@@ -105,4 +106,10 @@
                 .doesNotContain(false)
         }
     }
+
+    @Postsubmit
+    @Test
+    open fun pipLayerHasCorrectCornersAtEnd() {
+        flicker.assertLayersEnd { hasRoundedCorners(pipApp) }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90e3f7f..1e4b8b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -881,7 +881,7 @@
         RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
         if (mController.mBackAnimationAdapter != null) {
             mController.mBackAnimationAdapter.getRunner().onAnimationStart(
-                    targets, null, null, mBackAnimationFinishedCallback);
+                    targets, null /* prepareOpenTransition */, mBackAnimationFinishedCallback);
             mShellExecutor.flushAll();
         }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index b39cf19..d5287e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -35,9 +35,12 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -90,6 +93,9 @@
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private CompatUIController mController;
     private ShellInit mShellInit;
     @Mock
@@ -122,7 +128,6 @@
     private CompatUIConfiguration mCompatUIConfiguration;
     @Mock
     private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
-
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
@@ -132,6 +137,8 @@
     @NonNull
     private CompatUIStatusManager mCompatUIStatusManager;
 
+    private boolean mInDesktopModePredicateResult;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -157,7 +164,7 @@
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
                 mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
-                mCompatUIStatusManager) {
+                mCompatUIStatusManager, i -> mInDesktopModePredicateResult) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
@@ -685,6 +692,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
     public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
         taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
@@ -695,6 +703,34 @@
                 eq(mMockTaskListener));
     }
 
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+    @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+    public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
+        mInDesktopModePredicateResult = false;
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+        mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+        verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+        mInDesktopModePredicateResult = true;
+        mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+        verify(mController).removeLayouts(taskInfo.taskId);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+    @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+    public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
+        mInDesktopModePredicateResult = false;
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+        mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+        verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+        mInDesktopModePredicateResult = true;
+        mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+        verify(mController, never()).removeLayouts(taskInfo.taskId);
+    }
+
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
         return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
                 /* isFocused */ false, /* isTopActivityTransparent */ false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
new file mode 100644
index 0000000..b14f163
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+import android.graphics.Rect
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/**
+ * Test class for {@link DesktopActivityOrientationChangeHandler}
+ *
+ * Usage: atest WMShellUnitTests:DesktopActivityOrientationChangeHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
+class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    @Mock lateinit var testExecutor: ShellExecutor
+    @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+    @Mock lateinit var transitions: Transitions
+    @Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
+    @Mock lateinit var taskStackListener: TaskStackListenerImpl
+
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var handler: DesktopActivityOrientationChangeHandler
+    private lateinit var shellInit: ShellInit
+    private lateinit var taskRepository: DesktopModeTaskRepository
+    // Mock running tasks are registered here so we can get the list from mock shell task organizer.
+    private val runningTasks = mutableListOf<RunningTaskInfo>()
+
+    @Before
+    fun setUp() {
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(DesktopModeStatus::class.java)
+                .startMocking()
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+        shellInit = spy(ShellInit(testExecutor))
+        taskRepository = DesktopModeTaskRepository()
+        whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+
+        handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
+            taskStackListener, resizeTransitionHandler, taskRepository)
+
+        shellInit.init()
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+
+        runningTasks.clear()
+    }
+
+    @Test
+    fun instantiate_addInitCallback() {
+        verify(shellInit).addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>())
+    }
+
+    @Test
+    fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
+        whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+        clearInvocations(shellInit)
+
+        handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
+            taskStackListener, resizeTransitionHandler, taskRepository)
+
+        verify(shellInit, never()).addInitCallback(any(),
+            any<DesktopActivityOrientationChangeHandler>())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_resizeable_doNothing() {
+        val task = setUpFreeformTask()
+
+        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
+            SCREEN_ORIENTATION_LANDSCAPE)
+
+        verify(resizeTransitionHandler, never()).startTransition(any(), any())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_nonResizeableFullscreen_doNothing() {
+        val task = createFullscreenTask()
+        task.isResizeable = false
+        val activityInfo = ActivityInfo()
+        activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT
+        task.topActivityInfo = activityInfo
+        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+        taskRepository.addActiveTask(DEFAULT_DISPLAY, task.taskId)
+        taskRepository.updateTaskVisibility(DEFAULT_DISPLAY, task.taskId, visible = true)
+        runningTasks.add(task)
+
+        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
+            SCREEN_ORIENTATION_LANDSCAPE)
+
+        verify(resizeTransitionHandler, never()).startTransition(any(), any())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() {
+        val task = setUpFreeformTask(isResizeable = false)
+        val newTask = setUpFreeformTask(isResizeable = false,
+            orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT)
+
+        handler.handleActivityOrientationChange(task, newTask)
+
+        verify(resizeTransitionHandler, never()).startTransition(any(), any())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
+        val task = setUpFreeformTask(isResizeable = false)
+        taskRepository.updateTaskVisibility(task.displayId, task.taskId, visible = false)
+
+        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
+            SCREEN_ORIENTATION_LANDSCAPE)
+
+        verify(resizeTransitionHandler, never()).startTransition(any(), any())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() {
+        val task = setUpFreeformTask(isResizeable = false)
+        val oldBounds = task.configuration.windowConfiguration.bounds
+        val newTask = setUpFreeformTask(isResizeable = false,
+            orientation = SCREEN_ORIENTATION_LANDSCAPE)
+
+        handler.handleActivityOrientationChange(task, newTask)
+
+        val wct = getLatestResizeDesktopTaskWct()
+        val finalBounds = findBoundsChange(wct, newTask)
+        assertNotNull(finalBounds)
+        val finalWidth = finalBounds.width()
+        val finalHeight = finalBounds.height()
+        // Bounds is landscape.
+        assertTrue(finalWidth > finalHeight)
+        // Aspect ratio remains the same.
+        assertEquals(oldBounds.height() / oldBounds.width(), finalWidth / finalHeight)
+        // Anchor point for resizing is at the center.
+        assertEquals(oldBounds.centerX(), finalBounds.centerX())
+    }
+
+    @Test
+    fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() {
+        val oldBounds = Rect(0, 0, 500, 200)
+        val task = setUpFreeformTask(
+            isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE, bounds = oldBounds
+        )
+        val newTask = setUpFreeformTask(isResizeable = false, bounds = oldBounds)
+
+        handler.handleActivityOrientationChange(task, newTask)
+
+        val wct = getLatestResizeDesktopTaskWct()
+        val finalBounds = findBoundsChange(wct, newTask)
+        assertNotNull(finalBounds)
+        val finalWidth = finalBounds.width()
+        val finalHeight = finalBounds.height()
+        // Bounds is portrait.
+        assertTrue(finalHeight > finalWidth)
+        // Aspect ratio remains the same.
+        assertEquals(oldBounds.width() / oldBounds.height(), finalHeight / finalWidth)
+        // Anchor point for resizing is at the center.
+        assertEquals(oldBounds.centerX(), finalBounds.centerX())
+    }
+
+    private fun setUpFreeformTask(
+        displayId: Int = DEFAULT_DISPLAY,
+        isResizeable: Boolean = true,
+        orientation: Int = SCREEN_ORIENTATION_PORTRAIT,
+        bounds: Rect? = Rect(0, 0, 200, 500)
+    ): RunningTaskInfo {
+        val task = createFreeformTask(displayId, bounds)
+        val activityInfo = ActivityInfo()
+        activityInfo.screenOrientation = orientation
+        task.topActivityInfo = activityInfo
+        task.isResizeable = isResizeable
+        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+        taskRepository.addActiveTask(displayId, task.taskId)
+        taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
+        taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
+        runningTasks.add(task)
+        return task
+    }
+
+    private fun getLatestResizeDesktopTaskWct(
+        currentBounds: Rect? = null
+    ): WindowContainerTransaction {
+        val arg: ArgumentCaptor<WindowContainerTransaction> =
+            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        verify(resizeTransitionHandler, atLeastOnce())
+            .startTransition(capture(arg), eq(currentBounds))
+        return arg.value
+    }
+
+    private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+        wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index e49eb36..d399b20 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -22,6 +22,8 @@
 import android.graphics.Point
 import android.graphics.Rect
 import android.os.IBinder
+import android.os.SystemProperties
+import android.os.Trace
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
@@ -38,6 +40,7 @@
 import android.window.TransitionInfo.Change
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
@@ -86,7 +89,11 @@
   @JvmField
   @Rule
   val extendedMockitoRule =
-      ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
+      ExtendedMockitoRule.Builder(this)
+          .mockStatic(DesktopModeStatus::class.java)
+          .mockStatic(SystemProperties::class.java)
+          .mockStatic(Trace::class.java)
+          .build()!!
 
   private val testExecutor = mock<ShellExecutor>()
   private val mockShellInit = mock<ShellInit>()
@@ -695,6 +702,17 @@
     assertNotNull(sessionId)
     verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason))
     verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate))
+    ExtendedMockito.verify {
+        Trace.setCounter(
+            eq(Trace.TRACE_TAG_WINDOW_MANAGER),
+            eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME),
+            eq(taskUpdate.visibleTaskCount.toLong()))
+    }
+    ExtendedMockito.verify {
+        SystemProperties.set(
+            eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
+            eq(taskUpdate.visibleTaskCount.toString()))
+    }
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt
new file mode 100644
index 0000000..8fab410
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUtilsTest.kt
@@ -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 com.android.wm.shell.desktopmode
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeUtilsTest {
+    @Test
+    fun isTaskBoundsEqual_stableBoundsAreEqual_returnTrue() {
+        assertThat(isTaskBoundsEqual(task2Bounds, stableBounds)).isTrue()
+    }
+
+    @Test
+    fun isTaskBoundsEqual_stableBoundsAreNotEqual_returnFalse() {
+        assertThat(isTaskBoundsEqual(task4Bounds, stableBounds)).isFalse()
+    }
+
+    @Test
+    fun isTaskWidthOrHeightEqual_stableBoundsAreEqual_returnTrue() {
+        assertThat(isTaskWidthOrHeightEqual(task2Bounds, stableBounds)).isTrue()
+    }
+
+    @Test
+    fun isTaskWidthOrHeightEqual_stableBoundWidthIsEquals_returnTrue() {
+        assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
+    }
+
+    @Test
+    fun isTaskWidthOrHeightEqual_stableBoundHeightIsEquals_returnTrue() {
+        assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
+    }
+
+    @Test
+    fun isTaskWidthOrHeightEqual_stableBoundsWidthOrHeightAreNotEquals_returnFalse() {
+        assertThat(isTaskWidthOrHeightEqual(task1Bounds, stableBounds)).isTrue()
+    }
+
+    private companion object {
+        val task1Bounds = Rect(0, 0, 0, 0)
+        val task2Bounds = Rect(1, 1, 1, 1)
+        val task3Bounds = Rect(0, 1, 0, 1)
+        val task4Bounds = Rect(1, 2, 2, 1)
+        val stableBounds = Rect(1, 1, 1, 1)
+    }
+}
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 d248720..5474e53 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
@@ -84,6 +84,7 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -175,6 +176,7 @@
   @Mock
   private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
   @Mock private lateinit var mockSurface: SurfaceControl
+  @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
 
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
@@ -237,6 +239,8 @@
     val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
     verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
     recentsTransitionStateListener = captor.value
+
+    controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
   }
 
   private fun createController(): DesktopTasksController {
@@ -282,6 +286,52 @@
   }
 
   @Test
+  fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
+    setUpFreeformTask()
+
+    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
+  }
+
+  @Test
+  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
+    val task1 = setUpFreeformTask()
+
+    val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+    controller.toggleDesktopTaskSize(task1)
+    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+
+    assertThat(argumentCaptor.value).isTrue()
+  }
+
+  @Test
+  fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
+    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+    setUpFreeformTask(bounds = stableBounds, active = true)
+    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+  }
+
+  @Test
+  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() {
+    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+    val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
+
+    val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+    controller.toggleDesktopTaskSize(task1)
+    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+
+    assertThat(argumentCaptor.value).isFalse()
+  }
+
+  @Test
+  fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
+    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+    setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
+
+    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+  }
+
+
+  @Test
   fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
     whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
     clearInvocations(shellInit)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 16a234b..5b02837 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -8,6 +8,7 @@
 import android.app.WindowConfiguration.WindowingMode
 import android.graphics.PointF
 import android.os.IBinder
+import android.os.SystemProperties
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
@@ -16,6 +17,7 @@
 import android.window.TransitionInfo.FLAG_IS_WALLPAPER
 import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -29,19 +31,24 @@
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import java.util.function.Supplier
+import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.MockitoSession
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
 
 /** Tests of [DragToDesktopTransitionHandler]. */
 @SmallTest
@@ -61,10 +68,12 @@
 
     private lateinit var defaultHandler: DragToDesktopTransitionHandler
     private lateinit var springHandler: SpringDragToDesktopTransitionHandler
+    private lateinit var mockitoSession: MockitoSession
 
     @Before
     fun setUp() {
-        defaultHandler = DefaultDragToDesktopTransitionHandler(
+        defaultHandler =
+            DefaultDragToDesktopTransitionHandler(
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
@@ -72,7 +81,8 @@
                     transactionSupplier,
                 )
                 .apply { setSplitScreenController(splitScreenController) }
-        springHandler = SpringDragToDesktopTransitionHandler(
+        springHandler =
+            SpringDragToDesktopTransitionHandler(
                     context,
                     transitions,
                     taskDisplayAreaOrganizer,
@@ -80,6 +90,16 @@
                     transactionSupplier,
                 )
                 .apply { setSplitScreenController(splitScreenController) }
+        mockitoSession =
+            ExtendedMockito.mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(SystemProperties::class.java)
+                .startMocking()
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
     }
 
     @Test
@@ -357,6 +377,77 @@
         verify(finishCallback).onTransitionFinished(null)
     }
 
+    @Test
+    fun propertyValue_returnsSystemPropertyValue() {
+        val name = "property_name"
+        val value = 10f
+
+        whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+            .thenReturn(value.toInt())
+
+        assertEquals(
+            "Expects to return system properties stored value",
+            /* expected= */ value,
+            /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name)
+        )
+    }
+
+    @Test
+    fun propertyValue_withScale_returnsScaledSystemPropertyValue() {
+        val name = "property_name"
+        val value = 10f
+        val scale = 100f
+
+        whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+            .thenReturn(value.toInt())
+
+        assertEquals(
+            "Expects to return scaled system properties stored value",
+            /* expected= */ value / scale,
+            /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale)
+        )
+    }
+
+    @Test
+    fun propertyValue_notSet_returnsDefaultValue() {
+        val name = "property_name"
+        val defaultValue = 50f
+
+        whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(defaultValue.toInt())))
+            .thenReturn(defaultValue.toInt())
+
+        assertEquals(
+            "Expects to return the default value",
+            /* expected= */ defaultValue,
+            /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+                name,
+                default = defaultValue
+            )
+        )
+    }
+
+    @Test
+    fun propertyValue_withScaleNotSet_returnsDefaultValue() {
+        val name = "property_name"
+        val defaultValue = 0.5f
+        val scale = 100f
+        // Default value is multiplied when provided as a default value for [SystemProperties]
+        val scaledDefault = (defaultValue * scale).toInt()
+
+        whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(scaledDefault)))
+            .thenReturn(scaledDefault)
+
+        assertEquals(
+            "Expects to return the default value",
+            /* expected= */ defaultValue,
+            /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+                name,
+                default = defaultValue,
+                scale = scale
+            )
+        )
+    }
+
     private fun startDrag(
         handler: DragToDesktopTransitionHandler,
         task: RunningTaskInfo = createTask(),
@@ -462,4 +553,7 @@
             )
         }
     }
+
+    private fun systemPropertiesKey(name: String) =
+        "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
 }
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 da0aca7b..0b5c678 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
@@ -34,6 +34,7 @@
 import android.hardware.input.InputManager
 import android.net.Uri
 import android.os.Handler
+import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.CheckFlagsRule
@@ -79,6 +80,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -162,11 +164,14 @@
     @Mock private lateinit var mockWindowManager: IWindowManager
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
+    @Mock private lateinit var mockUserHandle: UserHandle
     @Mock private lateinit var mockToast: Toast
     private val bgExecutor = TestShellExecutor()
     @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
     @Mock private lateinit var mockTasksLimiter: DesktopTasksLimiter
     @Mock private lateinit var mockFreeformTaskTransitionStarter: FreeformTaskTransitionStarter
+    @Mock private lateinit var mockActivityOrientationChangeHandler:
+            DesktopActivityOrientationChangeHandler
     private lateinit var spyContext: TestableContext
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -220,7 +225,8 @@
                 mockRootTaskDisplayAreaOrganizer,
                 windowDecorByTaskIdSpy,
                 mockInteractionJankMonitor,
-                Optional.of(mockTasksLimiter)
+                Optional.of(mockTasksLimiter),
+                Optional.of(mockActivityOrientationChangeHandler)
         )
         desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -888,12 +894,12 @@
 
         openInBrowserListenerCaptor.value.accept(uri)
 
-        verify(spyContext).startActivity(argThat { intent ->
+        verify(spyContext).startActivityAsUser(argThat { intent ->
             intent.data == uri
                     && ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
                     && intent.categories.contains(Intent.CATEGORY_LAUNCHER)
                     && intent.action == Intent.ACTION_MAIN
-        })
+        }, eq(mockUserHandle))
     }
 
     @Test
@@ -1104,6 +1110,7 @@
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
+        whenever(decoration.user).thenReturn(mockUserHandle)
         if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
             whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
                 .thenReturn(true)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ca468fc..25fae76 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4567,8 +4567,8 @@
      *     when the request is flagged with {@link #AUDIOFOCUS_FLAG_DELAY_OK}.
      * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
      *     requesting audio focus.
-     * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request
-     *      is temporary, and focus will be abandonned shortly. Examples of transient requests are
+     * @param focusReqType use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request
+     *      is temporary, and focus will be abandoned shortly. Examples of transient requests are
      *      for the playback of driving directions, or notifications sounds.
      *      Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for
      *      the previous focus owner to keep playing if it ducks its audio output.
@@ -4593,13 +4593,13 @@
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public int requestAudioFocus(OnAudioFocusChangeListener l,
             @NonNull AudioAttributes requestAttributes,
-            int durationHint,
+            int focusReqType,
             int flags) throws IllegalArgumentException {
         if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) {
             throw new IllegalArgumentException("Invalid flags 0x"
                     + Integer.toHexString(flags).toUpperCase());
         }
-        return requestAudioFocus(l, requestAttributes, durationHint,
+        return requestAudioFocus(l, requestAttributes, focusReqType,
                 flags & AUDIOFOCUS_FLAGS_APPS,
                 null /* no AudioPolicy*/);
     }
@@ -4614,7 +4614,7 @@
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
      *     requesting audio focus.
-     * @param durationHint see the description of the same parameter in
+     * @param focusReqType see the description of the same parameter in
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
      *     {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and {@link #AUDIOFOCUS_FLAG_LOCK}.
@@ -4636,14 +4636,14 @@
     })
     public int requestAudioFocus(OnAudioFocusChangeListener l,
             @NonNull AudioAttributes requestAttributes,
-            int durationHint,
+            int focusReqType,
             int flags,
             AudioPolicy ap) throws IllegalArgumentException {
         // parameter checking
         if (requestAttributes == null) {
             throw new IllegalArgumentException("Illegal null AudioAttributes argument");
         }
-        if (!AudioFocusRequest.isValidFocusGain(durationHint)) {
+        if (!AudioFocusRequest.isValidFocusGain(focusReqType)) {
             throw new IllegalArgumentException("Invalid duration hint");
         }
         if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
@@ -4664,7 +4664,7 @@
                     "Illegal null audio policy when locking audio focus");
         }
 
-        final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint)
+        final AudioFocusRequest afr = new AudioFocusRequest.Builder(focusReqType)
                 .setOnAudioFocusChangeListenerInt(l, null /* no Handler for this legacy API */)
                 .setAudioAttributes(requestAttributes)
                 .setAcceptsDelayedFocusGain((flags & AUDIOFOCUS_FLAG_DELAY_OK)
@@ -5025,16 +5025,16 @@
      * to identify this use case.
      * @param streamType use STREAM_RING for focus requests when ringing, VOICE_CALL for
      *    the establishment of the call
-     * @param durationHint the type of focus request. AUDIOFOCUS_GAIN_TRANSIENT is recommended so
+     * @param focusReqType the type of focus request. AUDIOFOCUS_GAIN_TRANSIENT is recommended so
      *    media applications resume after a call
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void requestAudioFocusForCall(int streamType, int durationHint) {
+    public void requestAudioFocusForCall(int streamType, int focusReqType) {
         final IAudioService service = getService();
         try {
             service.requestAudioFocus(new AudioAttributes.Builder()
                         .setInternalLegacyStreamType(streamType).build(),
-                    durationHint, mICallBack, null,
+                    focusReqType, mICallBack, null,
                     AudioSystem.IN_VOICE_COMM_FOCUS_ID,
                     getContext().getOpPackageName(),
                     getContext().getAttributionTag(),
@@ -10128,6 +10128,24 @@
 
     /**
      * @hide
+     * Blocks until permission updates have propagated through the audio system.
+     * Only useful in tests, where adoptShellPermissions can change the permission state of
+     * an app without the app being killed.
+     */
+    @TestApi
+    @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature.
+    public void permissionUpdateBarrier() {
+        final IAudioService service = getService();
+        try {
+            service.permissionUpdateBarrier();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * @hide
      * Return the list of independent stream types for volume control.
      * A stream type is considered independent when the volume changes of that type do not
      * affect any other independent volume control stream type.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d20b7f0..a96562d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -101,6 +101,8 @@
 
     oneway void portEvent(in int portId, in int event, in @nullable PersistableBundle extras);
 
+    void permissionUpdateBarrier();
+
     // Java-only methods below.
     void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
 
@@ -250,7 +252,7 @@
 
     boolean isBluetoothA2dpOn();
 
-    int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
+    int requestAudioFocus(in AudioAttributes aa, int focusReqType, IBinder cb,
             IAudioFocusDispatcher fd, in String clientId, in String callingPackageName,
             in String attributionTag, int flags, IAudioPolicyCallback pcb, int sdk);
 
@@ -560,7 +562,7 @@
 
     long getMaxAdditionalOutputDeviceDelay(in AudioDeviceAttributes device);
 
-    int requestAudioFocusForTest(in AudioAttributes aa, int durationHint, IBinder cb,
+    int requestAudioFocusForTest(in AudioAttributes aa, int focusReqType, IBinder cb,
             in IAudioFocusDispatcher fd, in String clientId, in String callingPackageName,
             int flags, int uid, int sdk);
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 2c71ee0..92f6eaf 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -18,6 +18,7 @@
 
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -32,6 +33,7 @@
 import android.hardware.tv.tuner.FrontendScanType;
 import android.media.MediaCodec;
 import android.media.tv.TvInputService;
+import android.media.tv.flags.Flags;
 import android.media.tv.tuner.dvr.DvrPlayback;
 import android.media.tv.tuner.dvr.DvrRecorder;
 import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
@@ -2529,6 +2531,53 @@
     }
 
     /**
+     * Request a frontend by frontend type.
+     *
+     * <p> This API is used (before {@link #tune(FrontendSettings)}) if the applications want to
+     * select a frontend of a particular type for {@link #tune(FrontendSettings)} when there are
+     * multiple frontends of the same type present, allowing the system to select which one is
+     * applied. The applied frontend will be one of the not-in-use frontends. If all frontends are
+     * in-use, this API will reclaim and apply the frontend owned by the lowest priority client if
+     * current client has higher priority. Otherwise, this API will not apply any frontend and
+     * return {@link #RESULT_UNAVAILABLE}.
+     *
+     * @param desiredFrontendType the Type of the desired fronted. Should be one of
+     *                            {@link android.media.tv.tuner.frontend.FrontendSettings.Type}
+     * @return result status of open operation.
+     * @see #applyFrontend(FrontendInfo)
+     * @see #tune(FrontendSettings)
+     */
+    @Result
+    @FlaggedApi(Flags.FLAG_TUNER_W_APIS)
+    @RequiresPermission(
+        allOf = {"android.permission.TUNER_RESOURCE_ACCESS", "android.permission.ACCESS_TV_TUNER"})
+    public int applyFrontendByType(@FrontendSettings.Type int desiredFrontendType) {
+        mFrontendLock.lock();
+        try {
+            if (mFeOwnerTuner != null) {
+                Log.e(TAG, "Operation connot be done by sharee of tuner");
+                return RESULT_INVALID_STATE;
+            }
+            if (mFrontendHandle != null) {
+                Log.e(TAG, "A frontend has been opened before");
+                return RESULT_INVALID_STATE;
+            }
+
+            mDesiredFrontendId = null;
+            mFrontendType = desiredFrontendType;
+            if (DEBUG) {
+                Log.d(TAG, "Applying frontend with type " + mFrontendType);
+            }
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+                return RESULT_UNAVAILABLE;
+            }
+            return RESULT_SUCCESS;
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
      * Open a shared filter instance.
      *
      * @param context the context of the caller.
diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp
index 88938e2..8c68f21 100644
--- a/media/tests/mediatestutils/Android.bp
+++ b/media/tests/mediatestutils/Android.bp
@@ -27,9 +27,11 @@
     name: "mediatestutils",
     srcs: [
         "java/com/android/media/mediatestutils/TestUtils.java",
+        "java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java",
     ],
     static_libs: [
         "androidx.concurrent_concurrent-futures",
+        "androidx.test.runner",
         "guava",
         "mediatestutils_host",
     ],
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
new file mode 100644
index 0000000..c51b5de
--- /dev/null
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/PermissionUpdateBarrierRule.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.mediatestutils;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Barrier to wait for permission updates to propagate to audioserver, to avoid flakiness when using
+ * {@code com.android.compatability.common.util.AdoptShellPermissionsRule}. Note, this rule should
+ * <b> always </b> be placed after the adopt permission rule. Don't use rule when changing
+ * permission state in {@code @Before}, since that executes after all rules.
+ */
+public class PermissionUpdateBarrierRule implements TestRule {
+
+    private final Context mContext;
+
+    /**
+     * @param context the context to use
+     */
+    public PermissionUpdateBarrierRule(Context context) {
+        mContext = context;
+    }
+
+    public PermissionUpdateBarrierRule() {
+        this(InstrumentationRegistry.getInstrumentation().getContext());
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                mContext.getSystemService(AudioManager.class).permissionUpdateBarrier();
+                base.evaluate();
+            }
+        };
+    }
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 447e980..5b6b6c0 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -220,14 +220,15 @@
     field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     field public static final String CATEGORY_OTHER = "other";
     field public static final String CATEGORY_PAYMENT = "payment";
-    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH";
-    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2; // 0x2
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET = -1; // 0xffffffff
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC";
   }
 
   public abstract class HostApduService extends android.app.Service {
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 25a01b9..717e01e 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -94,7 +94,7 @@
   public final class CardEmulation {
     method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
-    method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String);
+    method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
     method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
   }
 
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 0605dbe..497309c 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -18,12 +18,12 @@
 
 import android.Manifest;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
@@ -155,17 +155,23 @@
      * Route to Device Host (DH).
      */
     @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
-    public static final String DH = "DH";
+    public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0;
     /**
      * Route to eSE.
      */
     @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
-    public static final String ESE = "ESE";
+    public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1;
     /**
      * Route to UICC.
      */
     @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
-    public static final String UICC = "UICC";
+    public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC = 2;
+
+    /**
+     * Route unset.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET = -1;
 
     static boolean sIsInitialized = false;
     static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
@@ -734,7 +740,7 @@
      *
      * @return the preferred payment service description
      */
-    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @RequiresPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
     @Nullable
     public CharSequence getDescriptionForPreferredPaymentService() {
         ApduServiceInfo serviceInfo = callServiceReturn(() ->
@@ -884,10 +890,12 @@
     }
 
     /** @hide */
-    @StringDef({
-        DH,
-        ESE,
-        UICC
+    @IntDef(prefix = "PROTOCOL_AND_TECHNOLOGY_ROUTE_",
+            value = {
+                    PROTOCOL_AND_TECHNOLOGY_ROUTE_DH,
+                    PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE,
+                    PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC,
+                    PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtocolAndTechnologyRoute {}
@@ -896,29 +904,32 @@
       * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
       * while this Activity is in the foreground.
       *
-      * The parameter set to null can be used to keep current values for that entry. Either
+      * The parameter set to {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET}
+      * can be used to keep current values for that entry. Either
       * Protocol Route or Technology Route should be override when calling this API, otherwise
       * throw {@link IllegalArgumentException}.
       * <p>
       * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
       * <pre>
       * protected void onResume() {
-      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+      *     mNfcAdapter.overrideRoutingTable(
+      *         this, {@link #PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE}, null);
       * }</pre>
       * </p>
       * Also activities must call {@link #recoverRoutingTable(Activity)}
       * when it goes to the background. Only the package of the
       * currently preferred service (the service set as preferred by the current foreground
       * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
-      * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
+      * current Default Wallet Role Holder {@link RoleManager#ROLE_WALLET}),
       * otherwise a call to this method will fail and throw {@link SecurityException}.
       * @param activity The Activity that requests NFC controller routing table to be changed.
-      * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
-      * @param technology Tech-A, Tech-B and Tech-F route destination, which can be "DH" or "UICC"
-      * or "ESE".
+      * @param protocol ISO-DEP route destination, where the possible inputs are defined
+      *                 in {@link ProtocolAndTechnologyRoute}.
+      * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs
+      *                   are defined in {@link ProtocolAndTechnologyRoute}
       * @throws SecurityException if the caller is not the preferred NFC service
       * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
-      * foreground, or both protocol route and technology route are null.
+      * foreground.
       * <p>
       * This is a high risk API and only included to support mainline effort
       * @hide
@@ -926,25 +937,36 @@
     @SystemApi
     @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
     public void overrideRoutingTable(
-            @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol,
-            @ProtocolAndTechnologyRoute @Nullable String technology) {
+            @NonNull Activity activity, @ProtocolAndTechnologyRoute int protocol,
+            @ProtocolAndTechnologyRoute int technology) {
         if (!activity.isResumed()) {
             throw new IllegalArgumentException("Activity must be resumed.");
         }
-        if (protocol == null && technology == null) {
-            throw new IllegalArgumentException(("Both Protocol and Technology are null."));
-        }
+        String protocolRoute = switch (protocol) {
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+            default -> throw new IllegalStateException("Unexpected value: " + protocol);
+        };
+        String technologyRoute = switch (technology) {
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_DH -> "DH";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE -> "ESE";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC -> "UICC";
+            case PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET -> null;
+            default -> throw new IllegalStateException("Unexpected value: " + protocol);
+        };
         callService(() ->
                 sService.overrideRoutingTable(
                     mContext.getUser().getIdentifier(),
-                    protocol,
-                    technology,
+                    protocolRoute,
+                    technologyRoute,
                     mContext.getPackageName()));
     }
 
     /**
      * Restore the NFC controller routing table,
-     * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+     * which was changed by {@link #overrideRoutingTable(Activity, int, int)}
      *
      * @param activity The Activity that requested NFC controller routing table to be changed.
      * @throws IllegalArgumentException if the caller is not in the foreground.
diff --git a/packages/CredentialManager/wear/res/values-sv/strings.xml b/packages/CredentialManager/wear/res/values-sv/strings.xml
index 2d0254a6..0d4d12f 100644
--- a/packages/CredentialManager/wear/res/values-sv/strings.xml
+++ b/packages/CredentialManager/wear/res/values-sv/strings.xml
@@ -23,7 +23,7 @@
     <string name="dialog_dismiss_button" msgid="989567669882005067">"Stäng"</string>
     <string name="dialog_continue_button" msgid="8630290044077052145">"Fortsätt"</string>
     <string name="dialog_sign_in_options_button" msgid="448002958902615054">"Inloggningsalternativ"</string>
-    <string name="sign_in_options_title" msgid="6720572645638986680">"Inloggningsalternativ"</string>
+    <string name="sign_in_options_title" msgid="6720572645638986680">"Inloggnings­alternativ"</string>
     <string name="provider_list_title" msgid="6803918216129492212">"Hantera inloggningar"</string>
     <string name="choose_sign_in_title" msgid="3616025924746872202">"Välj en inloggning"</string>
     <string name="choose_passkey_title" msgid="8459270617632817465">"Välj nyckel"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 94d835b..3c95fd8 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -29,7 +29,7 @@
     <string name="label_pages" msgid="7768589729282182230">"页数"</string>
     <string name="destination_default_text" msgid="5422708056807065710">"选择打印机"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"全部<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
-    <string name="template_page_range" msgid="428638530038286328">"范围 <xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
+    <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>页"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例如:1-5、8、11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"打印预览"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"安装 PDF 查看器以便预览"</string>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 5ca4579..10932be 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -29,7 +29,7 @@
     <string name="label_pages" msgid="7768589729282182230">"頁面"</string>
     <string name="destination_default_text" msgid="5422708056807065710">"選取印表機"</string>
     <string name="template_all_pages" msgid="3322235982020148762">"全部 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
-    <string name="template_page_range" msgid="428638530038286328">"範圍是 <xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
+    <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g> 頁"</string>
     <string name="pages_range_example" msgid="8558694453556945172">"例如:1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"列印預覽"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"安裝預覽所需的 PDF 檢視器"</string>
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
index 8a25726..0d4ef3c 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml
@@ -20,7 +20,7 @@
        xmlns:tools="http://schemas.android.com/tools"
        tools:targetApi="28"
        android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface" />
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
     <corners
         android:topLeftRadius="?android:attr/dialogCornerRadius"
         android:topRightRadius="0dp"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
index 7e626e5..3072772 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml
@@ -20,7 +20,7 @@
        xmlns:tools="http://schemas.android.com/tools"
        tools:targetApi="28"
        android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface" />
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
     <corners
         android:topLeftRadius="0dp"
         android:topRightRadius="?android:attr/dialogCornerRadius"
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
index 9f4980b..f1790f9 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml
@@ -20,7 +20,7 @@
        xmlns:tools="http://schemas.android.com/tools"
        tools:targetApi="28"
        android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface" />
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
     <corners
         android:radius="?android:attr/dialogCornerRadius"
     />
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
index 67b5107..f0da7b4 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml
@@ -18,7 +18,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorSurface" />
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
     <corners
         android:radius="0dp"
     />
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
new file mode 100644
index 0000000..35517ea
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+    <corners android:radius="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
new file mode 100644
index 0000000..18696c6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:drawable="@drawable/audio_sharing_rounded_bg"/>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index 1489e5f..b99219c 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -243,7 +243,7 @@
     <item msgid="8612549335720461635">"4K (seguro)"</item>
     <item msgid="7322156123728520872">"4K (mejorado)"</item>
     <item msgid="7735692090314849188">"4K (mejorado, seguro)"</item>
-    <item msgid="7346816300608639624">"720p, 1080p (pantalla doble)"</item>
+    <item msgid="7346816300608639624">"720p, 1080p (pantalla dual)"</item>
   </string-array>
   <string-array name="enable_opengl_traces_entries">
     <item msgid="4433736508877934305">"Desactivado"</item>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 00f43a3..6ed484c 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -27,7 +27,7 @@
     <item msgid="8356618438494652335">"Autentifikatzen…"</item>
     <item msgid="2837871868181677206">"IP helbidea lortzen…"</item>
     <item msgid="4613015005934755724">"Konektatuta"</item>
-    <item msgid="3763530049995655072">"Etenda"</item>
+    <item msgid="3763530049995655072">"Aldi baterako etenda"</item>
     <item msgid="7852381437933824454">"Deskonektatzen…"</item>
     <item msgid="5046795712175415059">"Deskonektatuta"</item>
     <item msgid="2473654476624070462">"Ezin izan da konektatu"</item>
@@ -41,7 +41,7 @@
     <item msgid="3028983857109369308">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> sarearekin autentifikatzen…"</item>
     <item msgid="4287401332778341890">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> sarearen IP helbidea lortzen…"</item>
     <item msgid="1043944043827424501">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> sarera konektatuta"</item>
-    <item msgid="7445993821842009653">"Etenda"</item>
+    <item msgid="7445993821842009653">"Aldi baterako etenda"</item>
     <item msgid="1175040558087735707">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> saretik deskonektatzen…"</item>
     <item msgid="699832486578171722">"Deskonektatuta"</item>
     <item msgid="522383512264986901">"Ezin izan da konektatu"</item>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index 5834982..d150538 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -243,7 +243,7 @@
     <item msgid="8612549335720461635">"‏4K (ایمن)"</item>
     <item msgid="7322156123728520872">"‏4K (ارتقا یافته)"</item>
     <item msgid="7735692090314849188">"‏4K (ارتقا یافته، ایمن)"</item>
-    <item msgid="7346816300608639624">"‫720p، ‫1080p ‫(Dual Screen)"</item>
+    <item msgid="7346816300608639624">"‏‫‫720p، ‫1080p ‫(صفحه‌نمایش دوگانه)"</item>
   </string-array>
   <string-array name="enable_opengl_traces_entries">
     <item msgid="4433736508877934305">"خالی"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 042504a..2297376 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -81,7 +81,7 @@
     <string name="speed_label_fast" msgid="2677719134596044051">"Cepat"</string>
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Sangat Cepat"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Sudah tidak berlaku"</string>
-    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"Sambungan terputus"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Memutus sambungan..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Menghubungkan…"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index a6bb5b8..2404dc2 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -235,7 +235,7 @@
     <item msgid="6946761421234586000">"400 %"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
-    <string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
+    <string name="category_personal" msgid="6236798763159385225">"Privat"</string>
     <string name="category_work" msgid="4014193632325996115">"Jobb"</string>
     <string name="category_private" msgid="4244892185452788977">"Privat"</string>
     <string name="category_clone" msgid="1554511758987195974">"Klon"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index e59b427..88e67f6 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -81,7 +81,7 @@
     <string name="speed_label_fast" msgid="2677719134596044051">"快"</string>
     <string name="speed_label_very_fast" msgid="8215718029533182439">"很快"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"已失效"</string>
-    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+    <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"已断开连接"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"正在断开连接..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"正在连接..."</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 055afed..0ffb763 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,5 +1,6 @@
 package com.android.settingslib.bluetooth;
 
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
 import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
 
 import android.annotation.SuppressLint;
@@ -704,12 +705,50 @@
         return !sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected);
     }
 
+    /**
+     * Check if {@link BluetoothDevice} has a active local broadcast source.
+     *
+     * @param device The bluetooth device to check.
+     * @param localBtManager The BT manager to provide BT functions.
+     * @return Whether the device has a active local broadcast source.
+     */
+    @WorkerThread
+    public static boolean hasActiveLocalBroadcastSourceForBtDevice(
+            @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                localBtManager == null
+                        ? null
+                        : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        LocalBluetoothLeBroadcast broadcast =
+                localBtManager == null
+                        ? null
+                        : localBtManager.getProfileManager().getLeAudioBroadcastProfile();
+        if (device == null || assistant == null || broadcast == null) {
+            Log.d(TAG, "Skip check hasActiveLocalBroadcastSourceForBtDevice due to arg is null");
+            return false;
+        }
+        List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(device);
+        int broadcastId = broadcast.getLatestBroadcastId();
+        return !sourceList.isEmpty()
+                && broadcastId != UNKNOWN_VALUE_PLACEHOLDER
+                && sourceList.stream()
+                        .anyMatch(
+                                source -> isSourceMatched(source, broadcastId));
+    }
+
     /** Checks the connectivity status based on the provided broadcast receive state. */
     @WorkerThread
     public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
         return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
     }
 
+    /** Checks if the broadcast id is matched based on the provided broadcast receive state. */
+    @WorkerThread
+    public static boolean isSourceMatched(
+            @Nullable BluetoothLeBroadcastReceiveState state, int broadcastId) {
+        return state != null && state.getBroadcastId() == broadcastId;
+    }
+
     /**
      * Checks if the Bluetooth device is an available hearing device, which means: 1) currently
      * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 26905b1..6f2567b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -101,7 +101,7 @@
     private static final int DEFAULT_CODE_MIN = 1000;
     // Order of this profile in device profiles list
     private static final int ORDINAL = 1;
-    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
     private static final Uri[] SETTINGS_URIS =
             new Uri[] {
                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_NAME),
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
index 91a99ae..24815fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.bluetooth
 
+import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothLeBroadcastAssistant
 import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -81,5 +82,9 @@
             ConcurrentUtils.DIRECT_EXECUTOR,
             callback,
         )
-        awaitClose { unregisterServiceCallBack(callback) }
+        awaitClose {
+            if (BluetoothAdapter.getDefaultAdapter()?.isEnabled == true) {
+                unregisterServiceCallBack(callback)
+            }
+        }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java
index 2099b33..e84a5d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingFooterPreference.java
@@ -31,7 +31,7 @@
     DeviceSettingFooterPreference(
             @NonNull String footerText,
             Bundle extras) {
-        super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+        super(DeviceSettingType.DEVICE_SETTING_TYPE_FOOTER);
         mFooterText = footerText;
         mExtras = extras;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreference.java
new file mode 100644
index 0000000..953e7cb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreference.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing a help button displayed on the top right corner of the page. */
+public class DeviceSettingHelpPreference extends DeviceSettingPreference implements Parcelable {
+
+    private final Intent mIntent;
+    private final Bundle mExtras;
+
+    DeviceSettingHelpPreference(@NonNull Intent intent, Bundle extras) {
+        super(DeviceSettingType.DEVICE_SETTING_TYPE_HELP);
+        mIntent = intent;
+        mExtras = extras;
+    }
+
+    /** Read a {@link DeviceSettingHelpPreference} from {@link Parcel}. */
+    @NonNull
+    public static DeviceSettingHelpPreference readFromParcel(@NonNull Parcel in) {
+        Intent intent = in.readParcelable(Intent.class.getClassLoader());
+        Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+        return new DeviceSettingHelpPreference(intent, extras);
+    }
+
+    public static final Creator<DeviceSettingHelpPreference> CREATOR =
+            new Creator<>() {
+                @Override
+                @NonNull
+                public DeviceSettingHelpPreference createFromParcel(@NonNull Parcel in) {
+                    in.readInt();
+                    return readFromParcel(in);
+                }
+
+                @Override
+                @NonNull
+                public DeviceSettingHelpPreference[] newArray(int size) {
+                    return new DeviceSettingHelpPreference[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeParcelable(mIntent, flags);
+        dest.writeBundle(mExtras);
+    }
+
+    /** Builder class for {@link DeviceSettingHelpPreference}. */
+    public static final class Builder {
+        private Intent mIntent;
+        private Bundle mExtras = Bundle.EMPTY;
+
+        /**
+         * Sets the intent of the preference, should be an activity intent.
+         *
+         * @param intent The intent to launch when clicked.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public DeviceSettingHelpPreference.Builder setIntent(@NonNull Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the extras bundle.
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public DeviceSettingHelpPreference.Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link DeviceSettingHelpPreference} object.
+         *
+         * @return Returns the built {@link DeviceSettingHelpPreference} object.
+         */
+        @NonNull
+        public DeviceSettingHelpPreference build() {
+            return new DeviceSettingHelpPreference(mIntent, mExtras);
+        }
+    }
+
+    /**
+     * Gets the intent to launch when clicked.
+     *
+     * @return The intent.
+     */
+    @NonNull
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
index 441e3f8..ae3bf5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
@@ -42,4 +42,7 @@
 
     /** Device setting type is footer preference. */
     int DEVICE_SETTING_TYPE_FOOTER = 3;
+
+    /** Device setting type is "help" preference. */
+    int DEVICE_SETTING_TYPE_HELP = 4;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
index 127275f..5656f38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -25,12 +25,13 @@
  *
  * @property mainContentItems The setting items to be shown in main page.
  * @property moreSettingsItems The setting items to be shown in more settings page.
- * @property moreSettingsFooter The footer in more settings page.
+ * @property moreSettingsHelpItem The help item displayed on the top right corner of the page.
  * @property extras Extra bundle
  */
 data class DeviceSettingsConfig(
     val mainContentItems: List<DeviceSettingItem>,
     val moreSettingsItems: List<DeviceSettingItem>,
+    val moreSettingsHelpItem: DeviceSettingItem?,
     val extras: Bundle = Bundle.EMPTY,
 ) : Parcelable {
 
@@ -40,6 +41,7 @@
         parcel.run {
             writeTypedList(mainContentItems)
             writeTypedList(moreSettingsItems)
+            writeParcelable(moreSettingsHelpItem, flags)
             writeBundle(extras)
         }
     }
@@ -59,7 +61,9 @@
                                 arrayListOf<DeviceSettingItem>().also {
                                     readTypedList(it, DeviceSettingItem.CREATOR)
                                 },
-                            extras = readBundle((Bundle::class.java.classLoader))!!,
+                            moreSettingsHelpItem = readParcelable(
+                                DeviceSettingItem::class.java.classLoader
+                            )
                         )
                     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index cded014..457d6a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingFooterPreference
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingHelpPreference
 import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
@@ -97,7 +98,8 @@
     private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
         DeviceSettingConfigModel(
             mainItems = mainContentItems.map { it.toModel() },
-            moreSettingsItems = moreSettingsItems.map { it.toModel() })
+            moreSettingsItems = moreSettingsItems.map { it.toModel() },
+            moreSettingsHelpItem = moreSettingsHelpItem?.toModel(), )
 
     private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
         return if (!TextUtils.isEmpty(preferenceKey)) {
@@ -154,6 +156,9 @@
             is DeviceSettingFooterPreference -> DeviceSettingModel.FooterPreference(
                 cachedDevice = cachedDevice,
                 id = settingId, footerText = pref.footerText)
+            is DeviceSettingHelpPreference -> DeviceSettingModel.HelpPreference(
+                cachedDevice = cachedDevice,
+                id = settingId, intent = pref.intent)
             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index 4062462..c1ac763 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -24,6 +24,11 @@
     val mainItems: List<DeviceSettingConfigItemModel>,
     /** Items need to be shown in device details more settings page. */
     val moreSettingsItems: List<DeviceSettingConfigItemModel>,
+    /**
+     * Help button which need to be shown on the top right corner of device details more settings
+     * page.
+     */
+    val moreSettingsHelpItem: DeviceSettingConfigItemModel?,
 )
 
 /** Models a device setting item in config. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index 5fd4d06..73648ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -59,6 +59,13 @@
         val footerText: String,
     ) : DeviceSettingModel
 
+    /** Models a help preference displayed on the top right corner of the fragment. */
+    data class HelpPreference(
+        override val cachedDevice: CachedBluetoothDevice,
+        @DeviceSettingId override val id: Int,
+        val intent: Intent,
+    ) : DeviceSettingModel
+
     /** Models an unknown preference. */
     data class Unknown(
         override val cachedDevice: CachedBluetoothDevice,
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 4f2329b..47a08eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -136,12 +136,10 @@
             return true;
         }
 
-        if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
-            // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
-            final int userId = UserHandle.getUserId(uid);
-            if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
-                return true;
-            }
+        // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+        final int userId = UserHandle.getUserId(uid);
+        if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+            return true;
         }
 
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt
deleted file mode 100644
index 02d684d..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt
+++ /dev/null
@@ -1,100 +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.settingslib.media.data.repository
-
-import android.media.AudioManager
-import android.media.IVolumeController
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.buffer
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.launch
-
-/** Returns [AudioManager.setVolumeController] events as a [Flow] */
-fun AudioManager.volumeControllerEvents(): Flow<VolumeControllerEvent> =
-    callbackFlow {
-            volumeController =
-                object : IVolumeController.Stub() {
-                    override fun displaySafeVolumeWarning(flags: Int) {
-                        launch { send(VolumeControllerEvent.DisplaySafeVolumeWarning(flags)) }
-                    }
-
-                    override fun volumeChanged(streamType: Int, flags: Int) {
-                        launch { send(VolumeControllerEvent.VolumeChanged(streamType, flags)) }
-                    }
-
-                    override fun masterMuteChanged(flags: Int) {
-                        launch { send(VolumeControllerEvent.MasterMuteChanged(flags)) }
-                    }
-
-                    override fun setLayoutDirection(layoutDirection: Int) {
-                        launch { send(VolumeControllerEvent.SetLayoutDirection(layoutDirection)) }
-                    }
-
-                    override fun dismiss() {
-                        launch { send(VolumeControllerEvent.Dismiss) }
-                    }
-
-                    override fun setA11yMode(mode: Int) {
-                        launch { send(VolumeControllerEvent.SetA11yMode(mode)) }
-                    }
-
-                    override fun displayCsdWarning(
-                        csdWarning: Int,
-                        displayDurationMs: Int,
-                    ) {
-                        launch {
-                            send(
-                                VolumeControllerEvent.DisplayCsdWarning(
-                                    csdWarning,
-                                    displayDurationMs,
-                                )
-                            )
-                        }
-                    }
-                }
-            awaitClose { volumeController = null }
-        }
-        .buffer()
-
-/** Models events received via [IVolumeController] */
-sealed interface VolumeControllerEvent {
-
-    /** @see [IVolumeController.displaySafeVolumeWarning] */
-    data class DisplaySafeVolumeWarning(val flags: Int) : VolumeControllerEvent
-
-    /** @see [IVolumeController.volumeChanged] */
-    data class VolumeChanged(val streamType: Int, val flags: Int) : VolumeControllerEvent
-
-    /** @see [IVolumeController.masterMuteChanged] */
-    data class MasterMuteChanged(val flags: Int) : VolumeControllerEvent
-
-    /** @see [IVolumeController.setLayoutDirection] */
-    data class SetLayoutDirection(val layoutDirection: Int) : VolumeControllerEvent
-
-    /** @see [IVolumeController.setA11yMode] */
-    data class SetA11yMode(val mode: Int) : VolumeControllerEvent
-
-    /** @see [IVolumeController.displayCsdWarning] */
-    data class DisplayCsdWarning(
-        val csdWarning: Int,
-        val displayDurationMs: Int,
-    ) : VolumeControllerEvent
-
-    /** @see [IVolumeController.dismiss] */
-    data object Dismiss : VolumeControllerEvent
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIcon.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIcon.java
new file mode 100644
index 0000000..001701e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIcon.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Icon of a Zen Mode, already loaded from the owner's resources (if specified) or from a default.
+ */
+public record ZenIcon(@NonNull Key key, @NonNull Drawable drawable) {
+
+    /**
+     * Key of a Zen Mode Icon.
+     *
+     * <p>{@link #resPackage()} will be null if the resource belongs to the system, and thus can
+     * be loaded with any {@code Context}.
+     */
+    public record Key(@Nullable String resPackage, @DrawableRes int resId) {
+
+        public Key {
+            checkArgument(resId != 0, "Resource id must be valid");
+        }
+
+        static Key forSystemResource(@DrawableRes int resId) {
+            return new Key(null, resId);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
new file mode 100644
index 0000000..0a0b65b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import android.app.AutomaticZenRule;
+
+import com.android.internal.R;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Known icon keys for zen modes that lack a custom {@link AutomaticZenRule#getIconResId()}, based
+ * on their {@link ZenMode.Kind} and {@link ZenMode#getType}.
+ */
+class ZenIconKeys {
+
+    /** The icon for Do Not Disturb mode. */
+    static final ZenIcon.Key MANUAL_DND = ZenIcon.Key.forSystemResource(
+            R.drawable.ic_zen_mode_type_special_dnd);
+
+    /**
+     * The default icon for implicit modes (they can also have a specific icon, if the user has
+     * chosen one via Settings).
+     */
+    static final ZenIcon.Key IMPLICIT_MODE_DEFAULT = ZenIcon.Key.forSystemResource(
+            R.drawable.ic_zen_mode_type_unknown);
+
+    private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of(
+            AutomaticZenRule.TYPE_UNKNOWN,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown),
+            AutomaticZenRule.TYPE_OTHER,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_other),
+            AutomaticZenRule.TYPE_SCHEDULE_TIME,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_time),
+            AutomaticZenRule.TYPE_SCHEDULE_CALENDAR,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_calendar),
+            AutomaticZenRule.TYPE_BEDTIME,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_bedtime),
+            AutomaticZenRule.TYPE_DRIVING,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_driving),
+            AutomaticZenRule.TYPE_IMMERSIVE,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_immersive),
+            AutomaticZenRule.TYPE_THEATER,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_theater),
+            AutomaticZenRule.TYPE_MANAGED,
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_managed)
+    );
+
+    private static final ZenIcon.Key FOR_UNEXPECTED_TYPE =
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown);
+
+    /** Default icon descriptors per mode {@link AutomaticZenRule.Type}. */
+    static ZenIcon.Key forType(@AutomaticZenRule.Type int ruleType) {
+        return TYPE_DEFAULTS.getOrDefault(ruleType, FOR_UNEXPECTED_TYPE);
+    }
+
+    private ZenIconKeys() { }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index 271d5c4..fe0f98a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -16,28 +16,24 @@
 
 package com.android.settingslib.notification.modes;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.DrawableRes;
-import android.annotation.Nullable;
-import android.app.AutomaticZenRule;
 import android.content.Context;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
-import android.service.notification.SystemZenRules;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LruCache;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.google.common.util.concurrent.FluentFuture;
-import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
@@ -54,7 +50,7 @@
     @Nullable // Until first usage
     private static ZenIconLoader sInstance;
 
-    private final LruCache<String, Drawable> mCache;
+    private final LruCache<ZenIcon.Key, Drawable> mCache;
     private final ListeningExecutorService mBackgroundExecutor;
 
     public static ZenIconLoader getInstance() {
@@ -64,90 +60,85 @@
         return sInstance;
     }
 
+    /** Replaces the singleton instance of {@link ZenIconLoader} by the provided one. */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public static void setInstance(@Nullable ZenIconLoader instance) {
+        sInstance = instance;
+    }
+
     private ZenIconLoader() {
         this(Executors.newFixedThreadPool(4));
     }
 
     @VisibleForTesting
-    ZenIconLoader(ExecutorService backgroundExecutor) {
+    public ZenIconLoader(ExecutorService backgroundExecutor) {
         mCache = new LruCache<>(50);
         mBackgroundExecutor =
                 MoreExecutors.listeningDecorator(backgroundExecutor);
     }
 
+    /**
+     * Loads the {@link Drawable} corresponding to a {@link ZenMode} in a background thread, and
+     * caches it for future calls.
+     *
+     * <p>The {@link ZenIcon#drawable()} will always correspond to the resource indicated by
+     * {@link ZenIcon#key()}. In turn, this will match the value of {@link ZenMode#getIconKey()}
+     * for the supplied mode -- except for the rare case where the mode has an apparently valid
+     * drawable resource id that we fail to load for some reason, thus needing a "fallback" icon.
+     */
     @NonNull
-    ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) {
-        if (rule.getIconResId() == 0) {
-            return Futures.immediateFuture(getFallbackIcon(context, rule.getType()));
-        }
+    public ListenableFuture<ZenIcon> getIcon(@NonNull Context context, @NonNull ZenMode mode) {
+        ZenIcon.Key key = mode.getIconKey();
 
-        return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId()))
-                .transform(icon ->
-                                icon != null ? icon : getFallbackIcon(context, rule.getType()),
-                        MoreExecutors.directExecutor());
+        return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ true))
+                .transformAsync(drawable ->
+                        drawable != null
+                            ? immediateFuture(new ZenIcon(key, drawable))
+                            : getFallbackIcon(context, mode),
+                mBackgroundExecutor);
+    }
+
+    private ListenableFuture<ZenIcon> getFallbackIcon(Context context, ZenMode mode) {
+        ZenIcon.Key key = ZenIconKeys.forType(mode.getType());
+        return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ false))
+                .transform(drawable -> {
+                    checkNotNull(drawable, "Couldn't load DEFAULT icon for mode %s!", mode);
+                    return new ZenIcon(key, drawable);
+                },
+                directExecutor());
     }
 
     @NonNull
-    private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg,
-            int iconResId) {
-        String cacheKey = pkg + ":" + iconResId;
+    private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context,
+            ZenIcon.Key key, boolean useMonochromeIfPresent) {
         synchronized (mCache) {
-            Drawable cachedValue = mCache.get(cacheKey);
+            Drawable cachedValue = mCache.get(key);
             if (cachedValue != null) {
                 return immediateFuture(cachedValue != MISSING ? cachedValue : null);
             }
         }
 
         return FluentFuture.from(mBackgroundExecutor.submit(() -> {
-            if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) {
-                return context.getDrawable(iconResId);
+            if (TextUtils.isEmpty(key.resPackage())) {
+                return context.getDrawable(key.resId());
             } else {
-                Context appContext = context.createPackageContext(pkg, 0);
-                Drawable appDrawable = appContext.getDrawable(iconResId);
-                return getMonochromeIconIfPresent(appDrawable);
+                Context appContext = context.createPackageContext(key.resPackage(), 0);
+                Drawable appDrawable = appContext.getDrawable(key.resId());
+                return useMonochromeIfPresent
+                        ? getMonochromeIconIfPresent(appDrawable)
+                        : appDrawable;
             }
         })).catching(Exception.class, ex -> {
             // If we cannot resolve the icon, then store MISSING in the cache below, so
             // we don't try again.
-            Log.e(TAG, "Error while loading icon " + cacheKey, ex);
+            Log.e(TAG, "Error while loading mode icon " + key, ex);
             return null;
-        }, MoreExecutors.directExecutor()).transform(drawable -> {
+        }, directExecutor()).transform(drawable -> {
             synchronized (mCache) {
-                mCache.put(cacheKey, drawable != null ? drawable : MISSING);
+                mCache.put(key, drawable != null ? drawable : MISSING);
             }
             return drawable;
-        }, MoreExecutors.directExecutor());
-    }
-
-    private static Drawable getFallbackIcon(Context context, int ruleType) {
-        int iconResIdFromType = getIconResourceIdFromType(ruleType);
-        return requireNonNull(context.getDrawable(iconResIdFromType));
-    }
-
-    /** Return the default icon resource associated to a {@link AutomaticZenRule.Type} */
-    @DrawableRes
-    public static int getIconResourceIdFromType(@AutomaticZenRule.Type int ruleType) {
-        return switch (ruleType) {
-            case AutomaticZenRule.TYPE_UNKNOWN ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_unknown;
-            case AutomaticZenRule.TYPE_OTHER ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_other;
-            case AutomaticZenRule.TYPE_SCHEDULE_TIME ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_schedule_time;
-            case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar;
-            case AutomaticZenRule.TYPE_BEDTIME ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_bedtime;
-            case AutomaticZenRule.TYPE_DRIVING ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_driving;
-            case AutomaticZenRule.TYPE_IMMERSIVE ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_immersive;
-            case AutomaticZenRule.TYPE_THEATER ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_theater;
-            case AutomaticZenRule.TYPE_MANAGED ->
-                    com.android.internal.R.drawable.ic_zen_mode_type_managed;
-            default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown;
-        };
+        }, directExecutor());
     }
 
     private static Drawable getMonochromeIconIfPresent(Drawable icon) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index d36b55f..36975c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -20,9 +20,9 @@
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
-import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
@@ -36,7 +36,6 @@
 import android.app.AutomaticZenRule;
 import android.app.NotificationManager;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -50,12 +49,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.settingslib.R;
-
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.Comparator;
 import java.util.Objects;
@@ -275,46 +270,32 @@
     }
 
     /**
-     * Returns an icon "key" that is guaranteed to be different if the icon is different. Note that
-     * the inverse is not true, i.e. two keys can be different and the icon still be visually the
-     * same.
+     * Returns the {@link ZenIcon.Key} corresponding to the icon resource for this mode. This can be
+     * either app-provided (via {@link AutomaticZenRule#setIconResId}, user-chosen (via the icon
+     * picker in Settings), or a default icon based on the mode {@link Kind} and {@link #getType}.
      */
     @NonNull
-    public String getIconKey() {
-        return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId();
-    }
-
-    /**
-     * Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
-     * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
-     * (in its monochrome variant, if available), or a default icon based on the mode type.
-     */
-    @NonNull
-    public ListenableFuture<Drawable> getIcon(@NonNull Context context,
-            @NonNull ZenIconLoader iconLoader) {
-        if (mKind == Kind.MANUAL_DND) {
-            return Futures.immediateFuture(requireNonNull(
-                    context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
+    public ZenIcon.Key getIconKey() {
+        if (isManualDnd()) {
+            return ZenIconKeys.MANUAL_DND;
         }
-
-        return iconLoader.getIcon(context, mRule);
-    }
-
-    /**
-     * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
-     * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
-     * suitable for places where showing the launcher icon of an app could be confusing, such as
-     * the status bar or lockscreen.
-     */
-    @NonNull
-    public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
-            @NonNull ZenIconLoader iconLoader) {
-        if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
-            return Futures.immediateFuture(requireNonNull(
-                    context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
+        if (mRule.getIconResId() != 0) {
+            if (isSystemOwned()) {
+                // System-owned rules can only have system icons.
+                return ZenIcon.Key.forSystemResource(mRule.getIconResId());
+            } else {
+                // Technically, the icon of an app-provided rule could be a system icon if the
+                // user chose one with the picker. However, we cannot know for sure.
+                return new ZenIcon.Key(mRule.getPackageName(), mRule.getIconResId());
+            }
+        } else {
+            // Using a default icon (which is always a system icon).
+            if (mKind == Kind.IMPLICIT) {
+                return ZenIconKeys.IMPLICIT_MODE_DEFAULT;
+            } else {
+                return ZenIconKeys.forType(getType());
+            }
         }
-
-        return iconLoader.getIcon(context, mRule);
     }
 
     @NonNull
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt
new file mode 100644
index 0000000..0fe385b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.model
+
+import android.media.IVolumeController
+
+/** Models events received via [IVolumeController] */
+sealed interface VolumeControllerEvent {
+
+    /** @see [IVolumeController.displaySafeVolumeWarning] */
+    data class DisplaySafeVolumeWarning(val flags: Int) : VolumeControllerEvent
+
+    /** @see [IVolumeController.volumeChanged] */
+    data class VolumeChanged(val streamType: Int, val flags: Int) : VolumeControllerEvent
+
+    /** @see [IVolumeController.masterMuteChanged] */
+    data class MasterMuteChanged(val flags: Int) : VolumeControllerEvent
+
+    /** @see [IVolumeController.setLayoutDirection] */
+    data class SetLayoutDirection(val layoutDirection: Int) : VolumeControllerEvent
+
+    /** @see [IVolumeController.setA11yMode] */
+    data class SetA11yMode(val mode: Int) : VolumeControllerEvent
+
+    /** @see [IVolumeController.displayCsdWarning] */
+    data class DisplayCsdWarning(
+        val csdWarning: Int,
+        val displayDurationMs: Int,
+    ) : VolumeControllerEvent
+
+    /** @see [IVolumeController.dismiss] */
+    data object Dismiss : VolumeControllerEvent
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0e71116..3e2d832 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -22,9 +22,12 @@
 import android.media.AudioManager
 import android.media.AudioManager.AudioDeviceCategory
 import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import android.media.IVolumeController
 import android.provider.Settings
+import android.util.Log
 import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
 import com.android.settingslib.volume.shared.AudioLogger
 import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
 import com.android.settingslib.volume.shared.model.AudioManagerEvent
@@ -36,10 +39,13 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
@@ -73,6 +79,11 @@
      */
     val communicationDevice: StateFlow<AudioDeviceInfo?>
 
+    /** Events from [AudioManager.setVolumeController] */
+    val volumeControllerEvents: Flow<VolumeControllerEvent>
+
+    fun init()
+
     /** State of the [AudioStream]. */
     fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
 
@@ -90,8 +101,9 @@
     suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
 
     /** Gets audio device category. */
-    @AudioDeviceCategory
-    suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
+    @AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
+
+    suspend fun notifyVolumeControllerVisible(isVisible: Boolean)
 }
 
 class AudioRepositoryImpl(
@@ -101,8 +113,10 @@
     private val backgroundCoroutineContext: CoroutineContext,
     private val coroutineScope: CoroutineScope,
     private val logger: AudioLogger,
+    shouldUseVolumeController: Boolean,
 ) : AudioRepository {
 
+    private val volumeController = ProducingVolumeController()
     private val streamSettingNames: Map<AudioStream, String> =
         mapOf(
             AudioStream(AudioManager.STREAM_VOICE_CALL) to Settings.System.VOLUME_VOICE,
@@ -116,12 +130,19 @@
             AudioStream(AudioManager.STREAM_ASSISTANT) to Settings.System.VOLUME_ASSISTANT,
         )
 
+    override val volumeControllerEvents: Flow<VolumeControllerEvent> =
+        if (shouldUseVolumeController) {
+            volumeController.events
+        } else {
+            emptyFlow()
+        }
+
     override val mode: StateFlow<Int> =
         callbackFlow {
-            val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
-            audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
-            awaitClose { audioManager.removeOnModeChangedListener(listener) }
-        }
+                val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
+                audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
+                awaitClose { audioManager.removeOnModeChangedListener(listener) }
+            }
             .onStart { emit(audioManager.mode) }
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
@@ -141,14 +162,14 @@
     override val communicationDevice: StateFlow<AudioDeviceInfo?>
         get() =
             callbackFlow {
-                val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
-                audioManager.addOnCommunicationDeviceChangedListener(
-                    ConcurrentUtils.DIRECT_EXECUTOR,
-                    listener,
-                )
+                    val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+                    audioManager.addOnCommunicationDeviceChangedListener(
+                        ConcurrentUtils.DIRECT_EXECUTOR,
+                        listener,
+                    )
 
-                awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
-            }
+                    awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+                }
                 .filterNotNull()
                 .map { audioManager.communicationDevice }
                 .onStart { emit(audioManager.communicationDevice) }
@@ -159,20 +180,30 @@
                     audioManager.communicationDevice,
                 )
 
+    override fun init() {
+        try {
+            audioManager.volumeController = volumeController
+        } catch (error: SecurityException) {
+            Log.wtf("AudioManager", "Unable to set the volume controller", error)
+        }
+    }
+
     override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
         return merge(
-            audioManagerEventsReceiver.events.filter {
-                if (it is StreamAudioManagerEvent) {
-                    it.audioStream == audioStream
-                } else {
-                    true
-                }
-            },
-            volumeSettingChanges(audioStream),
-        )
+                audioManagerEventsReceiver.events.filter {
+                    if (it is StreamAudioManagerEvent) {
+                        it.audioStream == audioStream
+                    } else {
+                        true
+                    }
+                },
+                volumeSettingChanges(audioStream),
+                volumeControllerEvents.filter { it is VolumeControllerEvent.VolumeChanged },
+            )
             .conflate()
             .map { getCurrentAudioStream(audioStream) }
             .onStart { emit(getCurrentAudioStream(audioStream)) }
+            .distinctUntilChanged()
             .onEach { logger.onVolumeUpdateReceived(audioStream, it) }
             .flowOn(backgroundCoroutineContext)
     }
@@ -228,6 +259,12 @@
         }
     }
 
+    override suspend fun notifyVolumeControllerVisible(isVisible: Boolean) {
+        withContext(backgroundCoroutineContext) {
+            audioManager.notifyVolumeControllerVisible(volumeController, isVisible)
+        }
+    }
+
     private fun getMinVolume(stream: AudioStream): Int =
         try {
             audioManager.getStreamMinVolume(stream.value)
@@ -253,3 +290,45 @@
         }
     }
 }
+
+private class ProducingVolumeController : IVolumeController.Stub() {
+
+    private val mutableEvents = MutableSharedFlow<VolumeControllerEvent>(extraBufferCapacity = 32)
+    val events = mutableEvents.asSharedFlow()
+
+    override fun displaySafeVolumeWarning(flags: Int) {
+        mutableEvents.tryEmit(VolumeControllerEvent.DisplaySafeVolumeWarning(flags))
+    }
+
+    override fun volumeChanged(streamType: Int, flags: Int) {
+        mutableEvents.tryEmit(VolumeControllerEvent.VolumeChanged(streamType, flags))
+    }
+
+    override fun masterMuteChanged(flags: Int) {
+        mutableEvents.tryEmit(VolumeControllerEvent.MasterMuteChanged(flags))
+    }
+
+    override fun setLayoutDirection(layoutDirection: Int) {
+        mutableEvents.tryEmit(VolumeControllerEvent.SetLayoutDirection(layoutDirection))
+    }
+
+    override fun dismiss() {
+        mutableEvents.tryEmit(VolumeControllerEvent.Dismiss)
+    }
+
+    override fun setA11yMode(mode: Int) {
+        mutableEvents.tryEmit(VolumeControllerEvent.SetA11yMode(mode))
+    }
+
+    override fun displayCsdWarning(
+        csdWarning: Int,
+        displayDurationMs: Int,
+    ) {
+        mutableEvents.tryEmit(
+            VolumeControllerEvent.DisplayCsdWarning(
+                csdWarning,
+                displayDurationMs,
+            )
+        )
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 0e43acb..52e6391 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -44,6 +44,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -111,6 +112,7 @@
                 testScope.testScheduler,
                 testScope.backgroundScope,
                 logger,
+                true,
             )
     }
 
@@ -261,8 +263,8 @@
     @Test
     fun getBluetoothAudioDeviceCategory() {
         testScope.runTest {
-            `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn(
-                AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
+            `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78"))
+                .thenReturn(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
 
             val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78")
             runCurrent()
@@ -271,6 +273,27 @@
         }
     }
 
+    @Test
+    fun useVolumeControllerDisabled_setVolumeController_notCalled() {
+        testScope.runTest {
+            underTest =
+                AudioRepositoryImpl(
+                    eventsReceiver,
+                    audioManager,
+                    contentResolver,
+                    testScope.testScheduler,
+                    testScope.backgroundScope,
+                    logger,
+                    false,
+                )
+
+            underTest.volumeControllerEvents.launchIn(backgroundScope)
+            runCurrent()
+
+            verify(audioManager, never()).volumeController = any()
+        }
+    }
+
     private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
         verify(audioManager)
             .addOnCommunicationDeviceChangedListener(
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt
similarity index 76%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt
rename to packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt
index 83b612d..f5c2f01 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.media.data.repository
+package com.android.settingslib.volume.data.repository
 
+import android.content.ContentResolver
 import android.media.AudioManager
 import android.media.IVolumeController
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -39,16 +42,32 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class AudioManagerVolumeControllerExtTest {
+class AudioRepositoryVolumeControllerEventsTest {
 
     private val testScope = TestScope()
 
     @Captor private lateinit var volumeControllerCaptor: ArgumentCaptor<IVolumeController>
     @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var contentResolver: ContentResolver
+
+    private val logger = FakeAudioRepositoryLogger()
+    private val eventsReceiver = FakeAudioManagerEventsReceiver()
+
+    private lateinit var underTest: AudioRepository
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        underTest =
+            AudioRepositoryImpl(
+                eventsReceiver,
+                audioManager,
+                contentResolver,
+                testScope.testScheduler,
+                testScope.backgroundScope,
+                logger,
+                true,
+            )
     }
 
     @Test
@@ -83,7 +102,7 @@
     ) =
         testScope.runTest {
             var event: VolumeControllerEvent? = null
-            audioManager.volumeControllerEvents().onEach { event = it }.launchIn(backgroundScope)
+            underTest.volumeControllerEvents.onEach { event = it }.launchIn(backgroundScope)
             runCurrent()
             verify(audioManager).volumeController = volumeControllerCaptor.capture()
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 3a7b0c7..a0e764a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,6 +16,7 @@
 package com.android.settingslib.bluetooth;
 
 import static com.android.settingslib.bluetooth.BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
 import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -96,6 +97,7 @@
                     + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
     private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
     private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
+    private static final int TEST_BROADCAST_ID = 25;
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -671,6 +673,34 @@
     }
 
     @Test
+    public void testHasActiveLocalBroadcastSourceForBtDevice_hasActiveLocalSource() {
+        when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+        when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        sourceList.add(mLeBroadcastReceiveState);
+        when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+        assertThat(
+                BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(
+                        mBluetoothDevice, mLocalBluetoothManager))
+                .isTrue();
+    }
+
+    @Test
+    public void testHasActiveLocalBroadcastSourceForBtDevice_noActiveLocalSource() {
+        when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        sourceList.add(mLeBroadcastReceiveState);
+        when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+        assertThat(
+                BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(
+                        mBluetoothDevice, mLocalBluetoothManager))
+                .isFalse();
+    }
+
+
+    @Test
     public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
         when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 0000000..134a56e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1 @@
+include /packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreferenceTest.java
new file mode 100644
index 0000000..5620ba3
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingHelpPreferenceTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingHelpPreferenceTest {
+
+    @Test
+    public void getMethods() {
+        Intent intent = new Intent();
+        DeviceSettingHelpPreference preference =
+                new DeviceSettingHelpPreference.Builder()
+                        .setIntent(intent)
+                        .setExtras(buildBundle("key1", "value1"))
+                        .build();
+
+        assertThat(preference.getIntent()).isSameInstanceAs(intent);
+        assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+    }
+
+    @Test
+    public void parcelOperation() {
+        Intent intent = new Intent("intent_action");
+        DeviceSettingHelpPreference preference =
+                new DeviceSettingHelpPreference.Builder()
+                        .setIntent(intent)
+                        .setExtras(buildBundle("key1", "value1"))
+                        .build();
+
+        DeviceSettingHelpPreference fromParcel = writeAndRead(preference);
+
+        assertThat(fromParcel.getIntent().getAction())
+                .isEqualTo(preference.getIntent().getAction());
+        assertThat(fromParcel.getExtras().getString("key1"))
+                .isEqualTo(preference.getExtras().getString("key1"));
+    }
+
+    private Bundle buildBundle(String key, String value) {
+        Bundle bundle = new Bundle();
+        bundle.putString(key, value);
+        return bundle;
+    }
+
+    private DeviceSettingHelpPreference writeAndRead(DeviceSettingHelpPreference preference) {
+        Parcel parcel = Parcel.obtain();
+        preference.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        DeviceSettingHelpPreference fromParcel =
+                DeviceSettingHelpPreference.CREATOR.createFromParcel(parcel);
+        return fromParcel;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 7223e90..a0a2658 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -50,6 +50,14 @@
                             null,
                             Bundle(),
                         )),
+                moreSettingsHelpItem = DeviceSettingItem(
+                    3,
+                    "package_name_2",
+                    "class_name_2",
+                    "intent_action_2",
+                    null,
+                    Bundle(),
+                ),
                 extras = Bundle().apply { putString("key1", "value1") },
             )
 
@@ -71,6 +79,10 @@
             .containsExactly("class_name_2")
         assertThat(fromParcel.moreSettingsItems.stream().map { it.intentAction }.toList())
             .containsExactly("intent_action_2")
+        assertThat(fromParcel.moreSettingsHelpItem?.settingId).isEqualTo(3)
+        assertThat(fromParcel.moreSettingsHelpItem?.packageName).isEqualTo("package_name_2")
+        assertThat(fromParcel.moreSettingsHelpItem?.className).isEqualTo("class_name_2")
+        assertThat(fromParcel.moreSettingsHelpItem?.intentAction).isEqualTo("intent_action_2")
     }
 
     private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 061d515..95ee46e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -28,6 +28,7 @@
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
 import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
 import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingHelpPreference
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
@@ -246,6 +247,28 @@
     }
 
     @Test
+    fun getDeviceSetting_helpPreference_success() {
+        testScope.runTest {
+            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+            `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+                    input ->
+                input
+                    .getArgument<IDeviceSettingsListener>(1)
+                    .onDeviceSettingsChanged(listOf(DEVICE_SETTING_HELP))
+            }
+            var setting: DeviceSettingModel? = null
+
+            underTest
+                .getDeviceSetting(cachedDevice, DEVICE_SETTING_ID_HELP)
+                .onEach { setting = it }
+                .launchIn(backgroundScope)
+            runCurrent()
+
+            assertDeviceSetting(setting!!, DEVICE_SETTING_HELP)
+        }
+    }
+
+    @Test
     fun getDeviceSetting_noConfig_returnNull() {
         testScope.runTest {
             `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -359,6 +382,12 @@
                     assertToggle(actual.toggles[i], pref.toggleInfos[i])
                 }
             }
+            is DeviceSettingModel.HelpPreference -> {
+                assertThat(serviceResponse.preference)
+                    .isInstanceOf(DeviceSettingHelpPreference::class.java)
+                val pref = serviceResponse.preference as DeviceSettingHelpPreference
+                assertThat(actual.intent).isSameInstanceAs(pref.intent)
+            }
             else -> {}
         }
     }
@@ -418,7 +447,7 @@
                 CONFIG_SERVICE_INTENT_ACTION +
                 "</DEVICE_SETTINGS_CONFIG_ACTION>"
         val DEVICE_INFO = DeviceInfo.Builder().setBluetoothAddress(BLUETOOTH_ADDRESS).build()
-
+        const val DEVICE_SETTING_ID_HELP = 12345
         val DEVICE_SETTING_ITEM_1 =
             DeviceSettingItem(
                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
@@ -431,6 +460,12 @@
                 SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
                 SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
                 SETTING_PROVIDER_SERVICE_INTENT_ACTION_2)
+        val DEVICE_SETTING_HELP_ITEM =
+            DeviceSettingItem(
+                DEVICE_SETTING_ID_HELP,
+                SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
+                SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
+                SETTING_PROVIDER_SERVICE_INTENT_ACTION_2)
         val DEVICE_SETTING_1 =
             DeviceSetting.Builder()
                 .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -460,10 +495,15 @@
                                 .build())
                         .build())
                 .build()
+        val DEVICE_SETTING_HELP = DeviceSetting.Builder()
+            .setSettingId(DEVICE_SETTING_ID_HELP)
+            .setPreference(DeviceSettingHelpPreference.Builder().setIntent(Intent()).build())
+            .build()
         val DEVICE_SETTING_CONFIG =
             DeviceSettingsConfig(
                 listOf(DEVICE_SETTING_ITEM_1),
                 listOf(DEVICE_SETTING_ITEM_2),
+                DEVICE_SETTING_HELP_ITEM,
             )
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java
index 20461e3..6eb5f5b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenIconLoaderTest.java
@@ -16,17 +16,12 @@
 
 package com.android.settingslib.notification.modes;
 
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.AutomaticZenRule;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.service.notification.ZenPolicy;
+import android.service.notification.SystemZenRules;
 
-import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.Before;
@@ -48,44 +43,73 @@
     }
 
     @Test
-    public void getIcon_systemOwnedRuleWithIcon_loads() throws Exception {
-        AutomaticZenRule systemRule = newRuleBuilder()
-                .setPackage("android")
+    public void getIcon_systemOwnedModeWithIcon_loads() throws Exception {
+        ZenMode mode = new TestModeBuilder()
+                .setPackage(SystemZenRules.PACKAGE_ANDROID)
                 .setIconResId(android.R.drawable.ic_media_play)
                 .build();
 
-        ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, systemRule);
-        assertThat(loadFuture.isDone()).isTrue();
-        assertThat(loadFuture.get()).isNotNull();
+        ZenIcon icon = mLoader.getIcon(mContext, mode).get();
+
+        assertThat(icon.drawable()).isNotNull();
+        assertThat(icon.key().resPackage()).isNull();
+        assertThat(icon.key().resId()).isEqualTo(android.R.drawable.ic_media_play);
     }
 
     @Test
-    public void getIcon_ruleWithoutSpecificIcon_loadsFallback() throws Exception {
-        AutomaticZenRule rule = newRuleBuilder()
+    public void getIcon_modeWithoutSpecificIcon_loadsFallback() throws Exception {
+        ZenMode mode = new TestModeBuilder()
                 .setType(AutomaticZenRule.TYPE_DRIVING)
                 .setPackage("com.blah")
                 .build();
 
-        ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
-        assertThat(loadFuture.isDone()).isTrue();
-        assertThat(loadFuture.get()).isNotNull();
+        ZenIcon icon = mLoader.getIcon(mContext, mode).get();
+
+        assertThat(icon.drawable()).isNotNull();
+        assertThat(icon.key().resPackage()).isNull();
+        assertThat(icon.key().resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_driving);
     }
 
     @Test
     public void getIcon_ruleWithAppIconWithLoadFailure_loadsFallback() throws Exception {
-        AutomaticZenRule rule = newRuleBuilder()
+        ZenMode mode = new TestModeBuilder()
                 .setType(AutomaticZenRule.TYPE_DRIVING)
                 .setPackage("com.blah")
                 .setIconResId(-123456)
                 .build();
 
-        ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
-        assertThat(loadFuture.get()).isNotNull();
+        ZenIcon icon = mLoader.getIcon(mContext, mode).get();
+
+        assertThat(icon.drawable()).isNotNull();
+        assertThat(icon.key().resPackage()).isNull();
+        assertThat(icon.key().resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_driving);
     }
 
-    private static AutomaticZenRule.Builder newRuleBuilder() {
-        return new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
-                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
-                .setZenPolicy(new ZenPolicy.Builder().build());
+    @Test
+    public void getIcon_cachesCustomIcons() throws Exception {
+        ZenMode mode = new TestModeBuilder()
+                .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                .setIconResId(android.R.drawable.ic_media_play)
+                .build();
+
+        ZenIcon iconOne = mLoader.getIcon(mContext, mode).get();
+        ZenIcon iconTwo = mLoader.getIcon(mContext, mode).get();
+
+        assertThat(iconOne.drawable()).isSameInstanceAs(iconTwo.drawable());
+    }
+
+    @Test
+    public void getIcon_cachesDefaultIcons() throws Exception {
+        ZenMode mode = new TestModeBuilder()
+                .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                .setType(AutomaticZenRule.TYPE_IMMERSIVE)
+                .build();
+
+        ZenIcon iconOne = mLoader.getIcon(mContext, mode).get();
+        ZenIcon iconTwo = mLoader.getIcon(mContext, mode).get();
+
+        assertThat(iconOne.drawable()).isSameInstanceAs(iconTwo.drawable());
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index f533e77..e64b0c6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -20,6 +20,7 @@
 import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
 import static android.app.AutomaticZenRule.TYPE_OTHER;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
 import static android.app.AutomaticZenRule.TYPE_THEATER;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
@@ -30,14 +31,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
 import android.app.AutomaticZenRule;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Parcel;
 import android.service.notification.Condition;
@@ -47,12 +41,9 @@
 
 import com.android.internal.R;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -289,72 +280,97 @@
     }
 
     @Test
-    public void getIcon_normalMode_loadsIconNormally() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-        ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+    public void getIconKey_normalModeWithCustomIcon_isCustomIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setType(TYPE_BEDTIME)
+                .setPackage("some.package")
+                .setIconResId(123)
+                .build();
 
-        ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
-                iconLoader);
+        ZenIcon.Key iconKey = mode.getIconKey();
 
-        verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+        assertThat(iconKey.resPackage()).isEqualTo("some.package");
+        assertThat(iconKey.resId()).isEqualTo(123);
     }
 
     @Test
-    public void getIcon_manualDnd_returnsFixedIcon() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+    public void getIconKey_systemOwnedModeWithCustomIcon_isCustomIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setType(TYPE_SCHEDULE_CALENDAR)
+                .setPackage(PACKAGE_ANDROID)
+                .setIconResId(123)
+                .build();
 
-        ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getIcon(
-                RuntimeEnvironment.getApplication(), iconLoader);
+        ZenIcon.Key iconKey = mode.getIconKey();
 
-        assertThat(future.isDone()).isTrue();
-        verify(iconLoader, never()).getIcon(any(), any());
+        assertThat(iconKey.resPackage()).isNull();
+        assertThat(iconKey.resId()).isEqualTo(123);
     }
 
     @Test
-    public void getIcon_implicitMode_loadsIconNormally() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-        ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
-                zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+    public void getIconKey_implicitModeWithCustomIcon_isCustomIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setId(ZenModeConfig.implicitRuleId("some.package"))
+                .setPackage("some.package")
+                .setIconResId(123)
+                .build();
 
-        ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
-                iconLoader);
+        ZenIcon.Key iconKey = mode.getIconKey();
 
-        verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
+        assertThat(iconKey.resPackage()).isEqualTo("some.package");
+        assertThat(iconKey.resId()).isEqualTo(123);
     }
 
     @Test
-    public void getLockscreenIcon_normalMode_loadsIconNormally() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-        ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+    public void getIconKey_manualDnd_isDndIcon() {
+        ZenIcon.Key iconKey = TestModeBuilder.MANUAL_DND_INACTIVE.getIconKey();
 
-        ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
-                RuntimeEnvironment.getApplication(), iconLoader);
-
-        verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+        assertThat(iconKey.resPackage()).isNull();
+        assertThat(iconKey.resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
     }
 
     @Test
-    public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+    public void getIconKey_normalModeWithoutCustomIcon_isModeTypeIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setType(TYPE_BEDTIME)
+                .setPackage("some.package")
+                .build();
 
-        ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
-                RuntimeEnvironment.getApplication(), iconLoader);
+        ZenIcon.Key iconKey = mode.getIconKey();
 
-        assertThat(future.isDone()).isTrue();
-        verify(iconLoader, never()).getIcon(any(), any());
+        assertThat(iconKey.resPackage()).isNull();
+        assertThat(iconKey.resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_bedtime);
     }
 
     @Test
-    public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
-        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
-        ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
-                zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+    public void getIconKey_systemOwnedModeWithoutCustomIcon_isModeTypeIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setType(TYPE_SCHEDULE_CALENDAR)
+                .setPackage(PACKAGE_ANDROID)
+                .build();
 
-        ListenableFuture<Drawable> future = mode.getLockscreenIcon(
-                RuntimeEnvironment.getApplication(), iconLoader);
+        ZenIcon.Key iconKey = mode.getIconKey();
 
-        assertThat(future.isDone()).isTrue();
-        verify(iconLoader, never()).getIcon(any(), any());
+        assertThat(iconKey.resPackage()).isNull();
+        assertThat(iconKey.resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+    }
+
+    @Test
+    public void getIconKey_implicitModeWithoutCustomIcon_isSpecialIcon() {
+        ZenMode mode = new TestModeBuilder()
+                .setId(ZenModeConfig.implicitRuleId("some.package"))
+                .setPackage("some_package")
+                .setType(TYPE_BEDTIME) // Type should be ignored.
+                .build();
+
+        ZenIcon.Key iconKey = mode.getIconKey();
+
+        assertThat(iconKey.resPackage()).isNull();
+        assertThat(iconKey.resId()).isEqualTo(
+                com.android.internal.R.drawable.ic_zen_mode_type_unknown);
     }
 
     private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 65937ea..f6e1057 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -340,7 +340,8 @@
                         new String[] {
                             String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN),
                             String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_ANDROID),
-                            String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS)
+                            String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_IOS),
+                            String.valueOf(Global.Wearable.PAIRED_DEVICE_OS_TYPE_NONE)
                         }));
         VALIDATORS.put(
                 Global.Wearable.COMPANION_BLE_ROLE,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c1bb55c..d26a906 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -949,36 +949,37 @@
     strict_mode: false,
 }
 
-// Disable for now. TODO(b/356666754) Re-enable it
-// android_ravenwood_test {
-//     name: "SystemUiRavenTests",
-//     srcs: [
-//         ":SystemUI-tests-utils",
-//         ":SystemUI-tests-multivalent",
-//         // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
-//         // use_resource_processor: true when better supported by soong
-//         ":SystemUIRobo-stub{.aapt.srcjar}",
-//     ],
-//     static_libs: [
-//         "SystemUI-core",
-//         "SystemUI-res",
-//         "SystemUI-tests-base",
-//         "androidx.test.uiautomator_uiautomator",
-//         "androidx.core_core-animation-testing",
-//         "androidx.test.ext.junit",
-//         "kosmos",
-//         "mockito-kotlin-nodeps",
-//     ],
-//     libs: [
-//         "android.test.runner",
-//         "android.test.base",
-//         "android.test.mock",
-//     ],
-//     auto_gen_config: true,
-//     plugins: [
-//         "dagger2-compiler",
-//     ],
-// }
+android_ravenwood_test {
+    name: "SystemUiRavenTests",
+    srcs: [
+        ":SystemUI-tests-utils",
+        ":SystemUI-tests-multivalent",
+        // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
+        // use_resource_processor: true when better supported by soong
+        ":SystemUIRobo-stub{.aapt.srcjar}",
+    ],
+    static_libs: [
+        "SystemUI-core",
+        "SystemUI-res",
+        "SystemUI-tests-base",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
+        "androidx.test.ext.junit",
+        "kosmos",
+        "kotlin-test",
+        "mockito-kotlin-nodeps",
+        "androidx.compose.runtime_runtime",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    auto_gen_config: true,
+    plugins: [
+        "dagger2-compiler",
+    ],
+}
 
 // Opt-out config for optimizing the SystemUI target using R8.
 // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f98b29a..d394976 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -370,6 +370,9 @@
 
     <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
 
+    <!-- Listen to keyboard shortcut events from input manager -->
+    <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
+
     <!-- To follow the grammatical gender preference -->
     <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
 
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 95e4b59..10d7352 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -3,3 +3,12 @@
 
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
+flag {
+  name: "bp_icon_a11y"
+  namespace: "biometrics_framework"
+  description: "Fixes biometric prompt icon not working as button with a11y"
+  bug: "359423579"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a129ac1..97206de 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -139,13 +139,6 @@
 }
 
 flag {
-    name: "notifications_heads_up_refactor"
-    namespace: "systemui"
-    description: "Use HeadsUpInteractor to feed HUN updates to the NSSL."
-    bug: "325936094"
-}
-
-flag {
    name: "notification_transparent_header_fix"
    namespace: "systemui"
    description: "fix the transparent group header issue for async header inflation."
@@ -370,6 +363,13 @@
 }
 
 flag {
+   name: "status_bar_signal_policy_refactor"
+   namespace: "systemui"
+   description: "Use a settings observer for airplane mode and make StatusBarSignalPolicy startable"
+   bug: "264539100"
+}
+
+flag {
     name: "status_bar_swipe_over_chip"
     namespace: "systemui"
     description: "Allow users to swipe over the status bar chip to open the shade"
@@ -380,6 +380,17 @@
 }
 
 flag {
+    name: "status_bar_always_check_underlying_networks"
+    namespace: "systemui"
+    description: "For status bar connectivity UI, always check underlying networks for wifi and "
+        "carrier merged information, regardless of the sepcified transport type"
+    bug: "352162710"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "status_bar_stop_updating_window_height"
     namespace: "systemui"
     description: "Don't have PhoneStatusBarView manually trigger an update of the height in "
@@ -391,6 +402,13 @@
 }
 
 flag {
+    name: "status_bar_ron_chips"
+    namespace: "systemui"
+    description: "Show rich ongoing notifications as chips in the status bar"
+    bug: "361346412"
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -1120,16 +1138,6 @@
 }
 
 flag {
-  name: "glanceable_hub_back_gesture"
-  namespace: "systemui"
-  description: "Enables back gesture on the glanceable hub"
-  bug: "346331399"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "glanceable_hub_allow_keyguard_when_dreaming"
   namespace: "systemui"
   description: "Allows users to exit dream to keyguard with glanceable hub enabled"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index aeba67b..270d751 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
@@ -56,7 +57,7 @@
     private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
     private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
     private val dialogFactory: BouncerDialogFactory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
     override val key = Scenes.Bouncer
 
     private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
@@ -66,7 +67,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -75,7 +76,7 @@
         modifier: Modifier,
     ) =
         BouncerScene(
-            viewModel = rememberViewModel { contentViewModelFactory.create() },
+            viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() },
             dialogFactory = dialogFactory,
             modifier = modifier,
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 872bef2..ed12776 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -31,7 +31,6 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -47,7 +46,6 @@
 import com.android.compose.animation.scene.transitions
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.internal.R.attr.focusable
-import com.android.systemui.Flags.glanceableHubBackGesture
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -198,15 +196,7 @@
             Box(modifier = Modifier.fillMaxSize())
         }
 
-        val userActions =
-            if (glanceableHubBackGesture()) {
-                mapOf(
-                    Swipe(SwipeDirection.End) to CommunalScenes.Blank,
-                    Back to CommunalScenes.Blank,
-                )
-            } else {
-                mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
-            }
+        val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
 
         scene(CommunalScenes.Communal, userActions = userActions) {
             CommunalScene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 6f415ea..be6a0f9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -545,21 +545,30 @@
 ) {
     val coroutineScope = rememberCoroutineScope()
     val liveContentKeys = remember { mutableListOf<String>() }
+    var communalContentPending by remember { mutableStateOf(true) }
 
     LaunchedEffect(communalContent) {
+        // Do nothing until any communal content comes in
+        if (communalContentPending && communalContent.isEmpty()) {
+            return@LaunchedEffect
+        }
+
         val prevLiveContentKeys = liveContentKeys.toList()
+        val newLiveContentKeys = communalContent.filter { it.isLiveContent() }.map { it.key }
         liveContentKeys.clear()
-        liveContentKeys.addAll(communalContent.filter { it.isLiveContent() }.map { it.key })
+        liveContentKeys.addAll(newLiveContentKeys)
 
-        // Find the first updated content
+        // Do nothing on first communal content since we don't have a delta
+        if (communalContentPending) {
+            communalContentPending = false
+            return@LaunchedEffect
+        }
+
+        // Do nothing if there is no new live content
         val indexOfFirstUpdatedContent =
-            liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
-
-        // Scroll if current position is behind the first updated content
+            newLiveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
         if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
-            // Launching with a scope to prevent the job from being canceled in the case of a
-            // recomposition during scrolling
-            coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
+            gridState.scrollToItem(indexOfFirstUpdatedContent)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 6750e41..54ffcf4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,10 +27,12 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -44,7 +46,7 @@
     private val dialogFactory: SystemUIDialogFactory,
     private val interactionHandler: WidgetInteractionHandler,
     private val widgetSection: CommunalAppWidgetSection,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
     override val key = Scenes.Communal
 
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
@@ -55,6 +57,10 @@
             )
             .asStateFlow()
 
+    override suspend fun onActivated(): Nothing {
+        awaitCancellation()
+    }
+
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         CommunalHub(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 672b8a7..dbe7538 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -50,7 +50,7 @@
     fun SceneScope.Content(
         modifier: Modifier = Modifier,
     ) {
-        val viewModel = rememberViewModel { viewModelFactory.create() }
+        val viewModel = rememberViewModel("LockscreenContent") { viewModelFactory.create() }
         val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
         if (!isContentVisible) {
             // If the content isn't supposed to be visible, show a large empty box as it's needed
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7f059d7..2029e9e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -25,6 +25,7 @@
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
@@ -39,7 +40,7 @@
 constructor(
     actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
     private val lockscreenContent: Lazy<LockscreenContent>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
     override val key = Scenes.Lockscreen
 
     private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
@@ -49,7 +50,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 5f4dc6e..18e1092 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -83,7 +83,7 @@
     fun SceneScope.HeadsUpNotifications() {
         SnoozeableHeadsUpNotificationSpace(
             stackScrollView = stackScrollView.get(),
-            viewModel = rememberViewModel { viewModelFactory.create() },
+            viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() },
         )
     }
 
@@ -107,7 +107,7 @@
 
         ConstrainedNotificationStack(
             stackScrollView = stackScrollView.get(),
-            viewModel = rememberViewModel { viewModelFactory.create() },
+            viewModel = rememberViewModel("Notifications") { viewModelFactory.create() },
             modifier =
                 modifier
                     .fillMaxWidth()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 808e666..5f7b1ad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.res.R
 import com.android.systemui.util.animation.MeasurementInput
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 object MediaCarousel {
     object Elements {
@@ -46,6 +47,7 @@
     }
 }
 
+@ExperimentalCoroutinesApi
 @Composable
 fun SceneScope.MediaCarousel(
     isVisible: Boolean,
@@ -54,7 +56,7 @@
     carouselController: MediaCarouselController,
     offsetProvider: (() -> IntOffset)? = null,
 ) {
-    if (!isVisible) {
+    if (!isVisible || carouselController.isLockedAndHidden()) {
         return
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 9e292d0..a2beba8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -321,7 +321,9 @@
     val minScrimOffset: () -> Float = { minScrimTop - maxScrimTop() }
 
     // The height of the scrim visible on screen when it is in its resting (collapsed) state.
-    val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+    val minVisibleScrimHeight: () -> Float = {
+        screenHeight - maxScrimTop() - with(density) { navBarHeight.toPx() }
+    }
 
     // we are not scrolled to the top unless the scrim is at its maximum offset.
     LaunchedEffect(viewModel, scrimOffset) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 666e324..e9c96ea 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
 import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -61,7 +62,7 @@
     private val shadeSession: SaveableSession,
     private val stackScrollView: Lazy<NotificationScrollView>,
     private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
 
     override val key = Scenes.NotificationsShade
 
@@ -72,7 +73,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -80,9 +81,10 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        val notificationsPlaceholderViewModel = rememberViewModel {
-            notificationsPlaceholderViewModelFactory.create()
-        }
+        val notificationsPlaceholderViewModel =
+            rememberViewModel("NotificationsShadeScene") {
+                notificationsPlaceholderViewModelFactory.create()
+            }
 
         OverlayShade(
             modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 1921624..671b012 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -98,7 +98,7 @@
                 else -> QSSceneAdapter.State.CLOSED
             }
         }
-        is TransitionState.Transition.ChangeCurrentScene ->
+        is TransitionState.Transition.ChangeScene ->
             with(transitionState) {
                 when {
                     isSplitShade -> UnsquishingQS(squishiness)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d16ba0d..d372577 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -81,6 +81,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -129,7 +130,7 @@
     private val statusBarIconController: StatusBarIconController,
     private val mediaCarouselController: MediaCarouselController,
     @Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
     override val key = Scenes.QuickSettings
 
     private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
@@ -139,7 +140,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -151,7 +152,9 @@
             notificationStackScrollView = notificationStackScrollView.get(),
             viewModelFactory = contentViewModelFactory,
             notificationsPlaceholderViewModel =
-                rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+                rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
+                    notificationsPlaceholderViewModelFactory.create()
+                },
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
             statusBarIconController = statusBarIconController,
@@ -178,10 +181,11 @@
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
 
-    val viewModel = rememberViewModel { viewModelFactory.create() }
-    val brightnessMirrorViewModel = rememberViewModel {
-        viewModel.brightnessMirrorViewModelFactory.create()
-    }
+    val viewModel = rememberViewModel("QuickSettingsScene-viewModel") { viewModelFactory.create() }
+    val brightnessMirrorViewModel =
+        rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
+            viewModel.brightnessMirrorViewModelFactory.create()
+        }
     val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index fb7c422..90d7da6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -74,7 +75,7 @@
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
 
     override val key = Scenes.QuickSettingsShade
 
@@ -85,11 +86,16 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
+    override suspend fun onActivated(): Nothing {
+        actionsViewModel.activate()
+    }
+
     @Composable
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        val viewModel = rememberViewModel { contentViewModelFactory.create() }
+        val viewModel =
+            rememberViewModel("QuickSettingsShadeScene") { contentViewModelFactory.create() }
         OverlayShade(
             viewModelFactory = viewModel.overlayShadeViewModelFactory,
             lockscreenContent = lockscreenContent,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 3e22105..cbbace4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -26,6 +26,7 @@
 import com.android.compose.animation.scene.animateSceneDpAsState
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.qs.ui.composable.QuickSettings
@@ -50,7 +51,7 @@
     private val notificationStackScrolLView: Lazy<NotificationScrollView>,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
     private val viewModelFactory: GoneSceneActionsViewModel.Factory,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
     override val key = Scenes.Gone
 
     private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
@@ -58,7 +59,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -74,7 +75,10 @@
         Spacer(modifier.fillMaxSize())
         SnoozeableHeadsUpNotificationSpace(
             stackScrollView = notificationStackScrolLView.get(),
-            viewModel = rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+            viewModel =
+                rememberViewModel("GoneScene") {
+                    notificationsPlaceholderViewModelFactory.create()
+                },
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt
new file mode 100644
index 0000000..d62befd
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.OverlayKey
+import com.android.systemui.lifecycle.Activatable
+
+/**
+ * Defines interface for classes that can describe an "overlay".
+ *
+ * In the scene framework, there can be multiple overlays in a single scene "container". The
+ * container takes care of rendering any current overlays and allowing overlays to be shown, hidden,
+ * or replaced based on a user action.
+ */
+interface Overlay : Activatable {
+    /** Uniquely-identifying key for this overlay. The key must be unique within its container. */
+    val key: OverlayKey
+
+    @Composable fun ContentScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index d5874d1..f9723d9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.ui.composable
 
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.Text
@@ -28,7 +30,10 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.UserAction
@@ -54,12 +59,18 @@
  *   and only the scenes on this container. In other words: (a) there should be no scene in this map
  *   that is not in the configuration for this container and (b) all scenes in the configuration
  *   must have entries in this map.
+ * @param overlayByKey Mapping of [Overlay] by [OverlayKey], ordered by z-order such that the last
+ *   overlay is rendered on top of all other overlays. It's critical that this map contains exactly
+ *   and only the overlays on this container. In other words: (a) there should be no overlay in this
+ *   map that is not in the configuration for this container and (b) all overlays in the
+ *   configuration must have entries in this map.
  * @param modifier A modifier.
  */
 @Composable
 fun SceneContainer(
     viewModel: SceneContainerViewModel,
     sceneByKey: Map<SceneKey, ComposableScene>,
+    overlayByKey: Map<OverlayKey, Overlay>,
     initialSceneKey: SceneKey,
     dataSourceDelegator: SceneDataSourceDelegator,
     modifier: Modifier = Modifier,
@@ -86,27 +97,36 @@
         onDispose { viewModel.setTransitionState(null) }
     }
 
-    val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
-        mutableStateMapOf()
-    }
+    val userActionsByContentKey: MutableMap<ContentKey, Map<UserAction, UserActionResult>> =
+        remember {
+            mutableStateMapOf()
+        }
+    // TODO(b/359173565): Add overlay user actions when the API is final.
     LaunchedEffect(currentSceneKey) {
         try {
             sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
-                userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
+                userActionsByContentKey[currentSceneKey] =
+                    viewModel.resolveSceneFamilies(userActions)
             }
         } finally {
-            userActionsBySceneKey[currentSceneKey] = emptyMap()
+            userActionsByContentKey[currentSceneKey] = emptyMap()
         }
     }
 
     Box(
-        modifier = Modifier.fillMaxSize(),
+        modifier =
+            Modifier.fillMaxSize().pointerInput(Unit) {
+                awaitEachGesture {
+                    awaitFirstDown(false)
+                    viewModel.onSceneContainerUserInputStarted()
+                }
+            },
     ) {
         SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
             sceneByKey.forEach { (sceneKey, composableScene) ->
                 scene(
                     key = sceneKey,
-                    userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+                    userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap())
                 ) {
                     // Activate the scene.
                     LaunchedEffect(composableScene) { composableScene.activate() }
@@ -119,6 +139,15 @@
                     }
                 }
             }
+            overlayByKey.forEach { (overlayKey, composableOverlay) ->
+                overlay(
+                    key = overlayKey,
+                    userActions = userActionsByContentKey.getOrDefault(overlayKey, emptyMap())
+                ) {
+                    // Render the overlay.
+                    with(composableOverlay) { this@overlay.Content(Modifier) }
+                }
+            }
         }
 
         BottomRightCornerRibbon(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 4b4b7ed..e12a8bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -18,7 +18,9 @@
 
 package com.android.systemui.scene.ui.composable
 
+import androidx.compose.runtime.snapshotFlow
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.observableTransitionState
@@ -52,6 +54,14 @@
                 initialValue = state.transitionState.currentScene,
             )
 
+    override val currentOverlays: StateFlow<Set<OverlayKey>> =
+        snapshotFlow { state.currentOverlays }
+            .stateIn(
+                scope = coroutineScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = emptySet(),
+            )
+
     override fun changeScene(
         toScene: SceneKey,
         transitionKey: TransitionKey?,
@@ -68,4 +78,29 @@
             scene = toScene,
         )
     }
+
+    override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        state.showOverlay(
+            overlay = overlay,
+            animationScope = coroutineScope,
+            transitionKey = transitionKey,
+        )
+    }
+
+    override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        state.hideOverlay(
+            overlay = overlay,
+            animationScope = coroutineScope,
+            transitionKey = transitionKey,
+        )
+    }
+
+    override fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey?) {
+        state.replaceOverlay(
+            from = from,
+            to = to,
+            animationScope = coroutineScope,
+            transitionKey = transitionKey,
+        )
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index 1fee874..022eb1f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -5,10 +5,16 @@
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.systemui.bouncer.ui.composable.Bouncer
 
+const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
+
 fun TransitionBuilder.lockscreenToBouncerTransition() {
     spec = tween(durationMillis = 500)
 
     translate(Bouncer.Elements.Content, y = 300.dp)
-    fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
-    fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+    fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
+        fade(Bouncer.Elements.Background)
+    }
+    fractionRange(start = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
+        fade(Bouncer.Elements.Content)
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 445ffcb..595bbb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -69,7 +69,7 @@
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
-    val viewModel = rememberViewModel { viewModelFactory.create() }
+    val viewModel = rememberViewModel("OverlayShade") { viewModelFactory.create() }
     val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
 
     Box(modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 8c53740..05a0119 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -129,7 +129,7 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    val viewModel = rememberViewModel { viewModelFactory.create() }
+    val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
     val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
     if (isDisabled) {
         return
@@ -287,7 +287,7 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    val viewModel = rememberViewModel { viewModelFactory.create() }
+    val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
     val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
     if (isDisabled) {
         return
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 853dc6f..b7c6edc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -82,6 +82,7 @@
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.composable.MediaContentPicker
@@ -160,7 +161,7 @@
     private val mediaCarouselController: MediaCarouselController,
     @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) private val qsMediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), ComposableScene {
 
     override val key = Scenes.Shade
 
@@ -168,7 +169,7 @@
         actionsViewModelFactory.create()
     }
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -181,9 +182,12 @@
     ) =
         ShadeScene(
             notificationStackScrollView.get(),
-            viewModel = rememberViewModel { contentViewModelFactory.create() },
+            viewModel =
+                rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() },
             notificationsPlaceholderViewModel =
-                rememberViewModel { notificationsPlaceholderViewModelFactory.create() },
+                rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
+                    notificationsPlaceholderViewModelFactory.create()
+                },
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
             statusBarIconController = statusBarIconController,
@@ -492,9 +496,10 @@
         }
     }
 
-    val brightnessMirrorViewModel = rememberViewModel {
-        viewModel.brightnessMirrorViewModelFactory.create()
-    }
+    val brightnessMirrorViewModel =
+        rememberViewModel("SplitShade-brightnessMirrorViewModel") {
+            viewModel.brightnessMirrorViewModelFactory.create()
+        }
     val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index abe079a..e15bc12 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,7 +28,7 @@
     layoutState: MutableSceneTransitionLayoutStateImpl,
     target: SceneKey,
     transitionKey: TransitionKey?,
-): TransitionState.Transition.ChangeCurrentScene? {
+): TransitionState.Transition.ChangeScene? {
     val transitionState = layoutState.transitionState
     if (transitionState.currentScene == target) {
         // This can happen in 3 different situations, for which there isn't anything else to do:
@@ -55,7 +55,7 @@
                 replacedTransition = null,
             )
         }
-        is TransitionState.Transition.ChangeCurrentScene -> {
+        is TransitionState.Transition.ChangeScene -> {
             val isInitiatedByUserInput = transitionState.isInitiatedByUserInput
 
             // A transition is currently running: first check whether `transition.toScene` or
@@ -139,7 +139,7 @@
     reversed: Boolean = false,
     fromScene: SceneKey = layoutState.transitionState.currentScene,
     chain: Boolean = true,
-): TransitionState.Transition.ChangeCurrentScene {
+): TransitionState.Transition.ChangeScene {
     val oneOffAnimation = OneOffAnimation()
     val targetProgress = if (reversed) 0f else 1f
     val transition =
@@ -184,7 +184,7 @@
     override val isInitiatedByUserInput: Boolean,
     replacedTransition: TransitionState.Transition?,
     private val oneOffAnimation: OneOffAnimation,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene, replacedTransition) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene, replacedTransition) {
     override val progress: Float
         get() = oneOffAnimation.progress
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 7787458..37e4daa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -18,27 +18,17 @@
 
 package com.android.compose.animation.scene
 
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastCoerceIn
-import com.android.compose.animation.scene.content.Scene
+import com.android.compose.animation.scene.content.Content
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
 
 internal interface DraggableHandler {
     /**
@@ -63,11 +53,11 @@
     fun onDrag(delta: Float): Float
 
     /**
-     * Starts a transition to a target scene.
+     * Stop the current drag with the given [velocity].
      *
      * @return the consumed [velocity]
      */
-    fun onStop(velocity: Float, canChangeScene: Boolean): Float
+    fun onStop(velocity: Float, canChangeContent: Boolean): Float
 }
 
 internal class DraggableHandlerImpl(
@@ -109,22 +99,30 @@
             return false
         }
 
-        val swipeTransition = dragController.swipeTransition
+        val swipeAnimation = dragController.swipeAnimation
 
         // Don't intercept a transition that is finishing.
-        if (swipeTransition.isFinishing) {
+        if (swipeAnimation.isFinishing) {
             return false
         }
 
         // Only intercept the current transition if one of the 2 swipes results is also a transition
-        // between the same pair of scenes.
-        val fromScene = swipeTransition._currentScene
-        val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
-        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
+        // between the same pair of contents.
+        val swipes = computeSwipes(startedPosition, pointersDown = 1)
+        val fromContent = layoutImpl.content(swipeAnimation.currentContent)
+        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
+        val currentScene = layoutImpl.state.currentScene
+        val contentTransition = swipeAnimation.contentTransition
         return (upOrLeft != null &&
-            swipeTransition.isTransitioningBetween(fromScene.key, upOrLeft.toScene)) ||
+            contentTransition.isTransitioningBetween(
+                fromContent.key,
+                upOrLeft.toContent(currentScene)
+            )) ||
             (downOrRight != null &&
-                swipeTransition.isTransitioningBetween(fromScene.key, downOrRight.toScene))
+                contentTransition.isTransitioningBetween(
+                    fromContent.key,
+                    downOrRight.toContent(currentScene)
+                ))
     }
 
     override fun onDragStarted(
@@ -140,65 +138,75 @@
             }
 
             // This [transition] was already driving the animation: simply take over it.
-            // Stop animating and start from where the current offset.
-            oldDragController.swipeTransition.cancelOffsetAnimation()
+            // Stop animating and start from the current offset.
+            val oldSwipeAnimation = oldDragController.swipeAnimation
+            oldSwipeAnimation.cancelOffsetAnimation()
 
             // We need to recompute the swipe results since this is a new gesture, and the
             // fromScene.userActions may have changed.
             val swipes = oldDragController.swipes
-            swipes.updateSwipesResults(oldDragController.swipeTransition._fromScene)
+            swipes.updateSwipesResults(
+                fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
+            )
 
-            // A new gesture should always create a new SwipeTransition. This way there cannot be
+            // A new gesture should always create a new SwipeAnimation. This way there cannot be
             // different gestures controlling the same transition.
-            val swipeTransition = SwipeTransition(oldDragController.swipeTransition)
-            swipes.updateSwipesResults(fromScene = swipeTransition._fromScene)
-            return updateDragController(swipes, swipeTransition)
+            val swipeAnimation = createSwipeAnimation(oldSwipeAnimation)
+            return updateDragController(swipes, swipeAnimation)
         }
 
-        val transitionState = layoutImpl.state.transitionState
-        val fromScene = layoutImpl.scene(transitionState.currentScene)
-        val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        val swipes = computeSwipes(startedPosition, pointersDown)
+        val fromContent = layoutImpl.contentForUserActions()
+
+        swipes.updateSwipesResults(fromContent)
         val result =
-            swipes.findUserActionResult(fromScene, overSlop, true)
-                // As we were unable to locate a valid target scene, the initial SwipeTransition
+            swipes.findUserActionResult(overSlop)
+                // As we were unable to locate a valid target scene, the initial SwipeAnimation
                 // cannot be defined. Consequently, a simple NoOp Controller will be returned.
                 ?: return NoOpDragController
 
-        return updateDragController(
-            swipes = swipes,
-            swipeTransition =
-                SwipeTransition(
-                    layoutImpl.state,
-                    coroutineScope,
-                    fromScene,
-                    result,
-                    swipes,
-                    layoutImpl,
-                    orientation,
-                )
-        )
+        val swipeAnimation = createSwipeAnimation(swipes, result)
+        return updateDragController(swipes, swipeAnimation)
     }
 
     private fun updateDragController(
         swipes: Swipes,
-        swipeTransition: SwipeTransition
-    ): DragController {
-        val newDragController = DragControllerImpl(this, swipes, swipeTransition)
-        newDragController.updateTransition(swipeTransition, force = true)
+        swipeAnimation: SwipeAnimation<*>
+    ): DragControllerImpl {
+        val newDragController = DragControllerImpl(this, swipes, swipeAnimation)
+        newDragController.updateTransition(swipeAnimation, force = true)
         dragController = newDragController
         return newDragController
     }
 
-    private fun computeSwipes(
-        fromScene: Scene,
-        startedPosition: Offset?,
-        pointersDown: Int
-    ): Swipes {
+    internal fun createSwipeAnimation(
+        swipes: Swipes,
+        result: UserActionResult,
+    ): SwipeAnimation<*> {
+        val upOrLeftResult = swipes.upOrLeftResult
+        val downOrRightResult = swipes.downOrRightResult
+        val isUpOrLeft =
+            when (result) {
+                upOrLeftResult -> true
+                downOrRightResult -> false
+                else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
+            }
+
+        return createSwipeAnimation(
+            layoutImpl,
+            layoutImpl.coroutineScope,
+            result,
+            isUpOrLeft,
+            orientation
+        )
+    }
+
+    private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
         val fromSource =
             startedPosition?.let { position ->
                 layoutImpl.swipeSourceDetector
                     .source(
-                        fromScene.targetSize,
+                        layoutImpl.lastSize,
                         position.round(),
                         layoutImpl.density,
                         orientation,
@@ -254,215 +262,197 @@
 private class DragControllerImpl(
     private val draggableHandler: DraggableHandlerImpl,
     val swipes: Swipes,
-    var swipeTransition: SwipeTransition,
+    var swipeAnimation: SwipeAnimation<*>,
 ) : DragController {
     val layoutState = draggableHandler.layoutImpl.state
 
     /**
      * Whether this handle is active. If this returns false, calling [onDrag] and [onStop] will do
-     * nothing. We should have only one active controller at a time
+     * nothing.
      */
     val isDrivingTransition: Boolean
-        get() = layoutState.transitionState == swipeTransition
+        get() = layoutState.transitionState == swipeAnimation.contentTransition
 
     init {
         check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" }
     }
 
-    fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
-        if (isDrivingTransition || force) {
-            layoutState.startTransition(newTransition)
+    fun updateTransition(newTransition: SwipeAnimation<*>, force: Boolean = false) {
+        if (force || isDrivingTransition) {
+            layoutState.startTransition(newTransition.contentTransition)
         }
 
-        swipeTransition = newTransition
+        val previous = swipeAnimation
+        swipeAnimation = newTransition
+
+        // Finish the previous transition.
+        if (previous != newTransition) {
+            layoutState.finishTransition(previous.contentTransition)
+        }
     }
 
     /**
      * We receive a [delta] that can be consumed to change the offset of the current
-     * [SwipeTransition].
+     * [SwipeAnimation].
      *
      * @return the consumed delta
      */
     override fun onDrag(delta: Float): Float {
-        if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) {
+        return onDrag(delta, swipeAnimation)
+    }
+
+    private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
+        if (delta == 0f || !isDrivingTransition || swipeAnimation.isFinishing) {
             return 0f
         }
 
-        val toScene = swipeTransition._toScene
-        val distance = swipeTransition.distance()
-        val previousOffset = swipeTransition.dragOffset
+        val toContent = swipeAnimation.toContent
+        val distance = swipeAnimation.distance()
+        val previousOffset = swipeAnimation.dragOffset
         val desiredOffset = previousOffset + delta
 
         fun hasReachedToSceneUpOrLeft() =
             distance < 0 &&
                 desiredOffset <= distance &&
-                swipes.upOrLeftResult?.toScene == toScene.key
+                swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent
 
         fun hasReachedToSceneDownOrRight() =
             distance > 0 &&
                 desiredOffset >= distance &&
-                swipes.downOrRightResult?.toScene == toScene.key
+                swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent
 
-        // Considering accelerated swipe: Change fromScene in the case where the user quickly swiped
-        // multiple times in the same direction to accelerate the transition from A => B then B => C
+        // Considering accelerated swipe: Change fromContent in the case where the user quickly
+        // swiped multiple times in the same direction to accelerate the transition from A => B then
+        // B => C.
         //
         // TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging
         //  twice before B has been reached
-        val hasReachedToScene =
-            swipeTransition._currentScene == toScene &&
+        val hasReachedToContent =
+            swipeAnimation.currentContent == toContent &&
                 (hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())
 
-        val fromScene: Scene
+        val fromContent: ContentKey
         val currentTransitionOffset: Float
         val newOffset: Float
         val consumedDelta: Float
-        if (hasReachedToScene) {
-            // The new transition will start from the current toScene
-            fromScene = toScene
-            // The current transition is completed (we have reached the distance)
+        if (hasReachedToContent) {
+            // The new transition will start from the current toContent.
+            fromContent = toContent
+
+            // The current transition is completed (we have reached the full swipe distance).
             currentTransitionOffset = distance
-            // The next transition will start with the remaining offset
+
+            // The next transition will start with the remaining offset.
             newOffset = desiredOffset - distance
             consumedDelta = delta
         } else {
-            fromScene = swipeTransition._fromScene
-            val desiredProgress = swipeTransition.computeProgress(desiredOffset)
-            // note: the distance could be negative if fromScene is aboveOrLeft of toScene.
+            fromContent = swipeAnimation.fromContent
+            val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
+
+            // Note: the distance could be negative if fromContent is above or to the left of
+            // toContent.
             currentTransitionOffset =
                 when {
                     distance == DistanceUnspecified ||
-                        swipeTransition.isWithinProgressRange(desiredProgress) -> desiredOffset
+                        swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
+                        desiredOffset
                     distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
                     else -> desiredOffset.fastCoerceIn(distance, 0f)
                 }
+
             // If there is a new transition, we will use the same offset
             newOffset = currentTransitionOffset
             consumedDelta = newOffset - previousOffset
         }
 
-        swipeTransition.dragOffset = currentTransitionOffset
+        swipeAnimation.dragOffset = currentTransitionOffset
 
-        val result =
-            swipes.findUserActionResult(
-                fromScene = fromScene,
-                directionOffset = newOffset,
-                updateSwipesResults = hasReachedToScene
-            )
+        if (hasReachedToContent) {
+            swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
+        }
+        val result = swipes.findUserActionResult(directionOffset = newOffset)
 
         if (result == null) {
-            onStop(velocity = delta, canChangeScene = true)
+            onStop(velocity = delta, canChangeContent = true)
             return 0f
         }
 
         val needNewTransition =
-            hasReachedToScene ||
-                result.toScene != swipeTransition.toScene ||
-                result.transitionKey != swipeTransition.key
+            hasReachedToContent ||
+                result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
+                result.transitionKey != swipeAnimation.contentTransition.key
 
         if (needNewTransition) {
             // Make sure the current transition will finish to the right current scene.
-            swipeTransition._currentScene = fromScene
+            swipeAnimation.currentContent = fromContent
 
-            val newSwipeTransition =
-                SwipeTransition(
-                    layoutState = layoutState,
-                    coroutineScope = draggableHandler.coroutineScope,
-                    fromScene = fromScene,
-                    result = result,
-                    swipes = swipes,
-                    layoutImpl = draggableHandler.layoutImpl,
-                    orientation = draggableHandler.orientation,
-                )
-            newSwipeTransition.dragOffset = newOffset
-            updateTransition(newSwipeTransition)
+            val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
+            newSwipeAnimation.dragOffset = newOffset
+            updateTransition(newSwipeAnimation)
         }
 
         return consumedDelta
     }
 
-    override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
+    override fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+        return onStop(velocity, canChangeContent, swipeAnimation)
+    }
+
+    private fun <T : ContentKey> onStop(
+        velocity: Float,
+        canChangeContent: Boolean,
+
+        // Important: Make sure that this has the same name as [this.swipeAnimation] so that all the
+        // code here references the current animation when [onDragStopped] is called, otherwise the
+        // callbacks (like onAnimationCompleted()) might incorrectly finish a new transition that
+        // replaced this one.
+        swipeAnimation: SwipeAnimation<T>,
+    ): Float {
         // The state was changed since the drag started; don't do anything.
-        if (!isDrivingTransition || swipeTransition.isFinishing) {
+        if (!isDrivingTransition || swipeAnimation.isFinishing) {
             return 0f
         }
 
-        // Important: Make sure that all the code here references the current transition when
-        // [onDragStopped] is called, otherwise the callbacks (like onAnimationCompleted()) might
-        // incorrectly finish a new transition that replaced this one.
-        val swipeTransition = this.swipeTransition
-
-        fun animateTo(targetScene: Scene, targetOffset: Float) {
-            // If the effective current scene changed, it should be reflected right now in the
-            // current scene state, even before the settle animation is ongoing. That way all the
-            // swipeables and back handlers will be refreshed and the user can for instance quickly
-            // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
-            // immediately go back B => A.
-            if (targetScene != swipeTransition._currentScene) {
-                swipeTransition._currentScene = targetScene
-            }
-
-            swipeTransition.animateOffset(
-                coroutineScope = draggableHandler.coroutineScope,
+        fun animateTo(targetContent: T) {
+            swipeAnimation.animateOffset(
                 initialVelocity = velocity,
-                targetOffset = targetOffset,
-                targetScene = targetScene.key,
+                targetContent = targetContent,
             )
         }
 
-        val fromScene = swipeTransition._fromScene
-        if (canChangeScene) {
-            // If we are halfway between two scenes, we check what the target will be based on the
+        val fromContent = swipeAnimation.fromContent
+        if (canChangeContent) {
+            // If we are halfway between two contents, we check what the target will be based on the
             // velocity and offset of the transition, then we launch the animation.
 
-            val toScene = swipeTransition._toScene
+            val toContent = swipeAnimation.toContent
 
-            // Compute the destination scene (and therefore offset) to settle in.
-            val offset = swipeTransition.dragOffset
-            val distance = swipeTransition.distance()
-            var targetScene: Scene
-            var targetOffset: Float
-            if (
-                distance != DistanceUnspecified &&
-                    shouldCommitSwipe(
-                        offset = offset,
-                        distance = distance,
-                        velocity = velocity,
-                        wasCommitted = swipeTransition._currentScene == toScene,
-                        requiresFullDistanceSwipe = swipeTransition.requiresFullDistanceSwipe,
-                    )
-            ) {
-                targetScene = toScene
-                targetOffset = distance
-            } else {
-                targetScene = fromScene
-                targetOffset = 0f
-            }
+            // Compute the destination content (and therefore offset) to settle in.
+            val offset = swipeAnimation.dragOffset
+            val distance = swipeAnimation.distance()
+            val targetContent =
+                if (
+                    distance != DistanceUnspecified &&
+                        shouldCommitSwipe(
+                            offset = offset,
+                            distance = distance,
+                            velocity = velocity,
+                            wasCommitted = swipeAnimation.currentContent == toContent,
+                            requiresFullDistanceSwipe = swipeAnimation.requiresFullDistanceSwipe,
+                        )
+                ) {
+                    toContent
+                } else {
+                    fromContent
+                }
 
-            if (
-                targetScene != swipeTransition._currentScene &&
-                    !layoutState.canChangeScene(targetScene.key)
-            ) {
-                // We wanted to change to a new scene but we are not allowed to, so we animate back
-                // to the current scene.
-                targetScene = swipeTransition._currentScene
-                targetOffset =
-                    if (targetScene == fromScene) {
-                        0f
-                    } else {
-                        check(distance != DistanceUnspecified) {
-                            "distance is equal to $DistanceUnspecified"
-                        }
-                        distance
-                    }
-            }
-
-            animateTo(targetScene = targetScene, targetOffset = targetOffset)
+            animateTo(targetContent = targetContent)
         } else {
             // We are doing an overscroll preview animation between scenes.
-            check(fromScene == swipeTransition._currentScene) {
-                "canChangeScene is false but currentScene != fromScene"
+            check(fromContent == swipeAnimation.currentContent) {
+                "canChangeContent is false but currentContent != fromContent"
             }
-            animateTo(targetScene = fromScene, targetOffset = 0f)
+            animateTo(targetContent = fromContent)
         }
 
         // The onStop animation consumes any remaining velocity.
@@ -513,329 +503,8 @@
     }
 }
 
-private fun SwipeTransition(
-    layoutState: MutableSceneTransitionLayoutStateImpl,
-    coroutineScope: CoroutineScope,
-    fromScene: Scene,
-    result: UserActionResult,
-    swipes: Swipes,
-    layoutImpl: SceneTransitionLayoutImpl,
-    orientation: Orientation,
-): SwipeTransition {
-    val upOrLeftResult = swipes.upOrLeftResult
-    val downOrRightResult = swipes.downOrRightResult
-    val isUpOrLeft =
-        when (result) {
-            upOrLeftResult -> true
-            downOrRightResult -> false
-            else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
-        }
-
-    return SwipeTransition(
-        layoutImpl = layoutImpl,
-        layoutState = layoutState,
-        coroutineScope = coroutineScope,
-        key = result.transitionKey,
-        _fromScene = fromScene,
-        _toScene = layoutImpl.scene(result.toScene),
-        userActionDistanceScope = layoutImpl.userActionDistanceScope,
-        orientation = orientation,
-        isUpOrLeft = isUpOrLeft,
-        requiresFullDistanceSwipe = result.requiresFullDistanceSwipe,
-        replacedTransition = null,
-    )
-}
-
-private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
-    return SwipeTransition(
-            layoutImpl = old.layoutImpl,
-            layoutState = old.layoutState,
-            coroutineScope = old.coroutineScope,
-            key = old.key,
-            _fromScene = old._fromScene,
-            _toScene = old._toScene,
-            userActionDistanceScope = old.userActionDistanceScope,
-            orientation = old.orientation,
-            isUpOrLeft = old.isUpOrLeft,
-            lastDistance = old.lastDistance,
-            requiresFullDistanceSwipe = old.requiresFullDistanceSwipe,
-            replacedTransition = old,
-        )
-        .apply {
-            _currentScene = old._currentScene
-            dragOffset = old.dragOffset
-        }
-}
-
-private class SwipeTransition(
-    val layoutImpl: SceneTransitionLayoutImpl,
-    val layoutState: MutableSceneTransitionLayoutStateImpl,
-    val coroutineScope: CoroutineScope,
-    override val key: TransitionKey?,
-    val _fromScene: Scene,
-    val _toScene: Scene,
-    val userActionDistanceScope: UserActionDistanceScope,
-    override val orientation: Orientation,
-    override val isUpOrLeft: Boolean,
-    val requiresFullDistanceSwipe: Boolean,
-    replacedTransition: SwipeTransition?,
-    var lastDistance: Float = DistanceUnspecified,
-) :
-    TransitionState.Transition.ChangeCurrentScene(_fromScene.key, _toScene.key, replacedTransition),
-    TransitionState.HasOverscrollProperties {
-    var _currentScene by mutableStateOf(_fromScene)
-    override val currentScene: SceneKey
-        get() = _currentScene.key
-
-    override val progress: Float
-        get() {
-            // Important: If we are going to return early because distance is equal to 0, we should
-            // still make sure we read the offset before returning so that the calling code still
-            // subscribes to the offset value.
-            val offset = offsetAnimation?.animatable?.value ?: dragOffset
-
-            return computeProgress(offset)
-        }
-
-    fun computeProgress(offset: Float): Float {
-        val distance = distance()
-        if (distance == DistanceUnspecified) {
-            return 0f
-        }
-        return offset / distance
-    }
-
-    override val progressVelocity: Float
-        get() {
-            val animatable = offsetAnimation?.animatable ?: return 0f
-            val distance = distance()
-            if (distance == DistanceUnspecified) {
-                return 0f
-            }
-
-            val velocityInDistanceUnit = animatable.velocity
-            return velocityInDistanceUnit / distance.absoluteValue
-        }
-
-    override val isInitiatedByUserInput = true
-
-    override var bouncingContent: SceneKey? = null
-
-    /** The current offset caused by the drag gesture. */
-    var dragOffset by mutableFloatStateOf(0f)
-
-    /** The offset animation that animates the offset once the user lifts their finger. */
-    private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
-
-    override val isUserInputOngoing: Boolean
-        get() = offsetAnimation == null
-
-    override val overscrollScope: OverscrollScope =
-        object : OverscrollScope {
-            override val density: Float
-                get() = layoutImpl.density.density
-
-            override val fontScale: Float
-                get() = layoutImpl.density.fontScale
-
-            override val absoluteDistance: Float
-                get() = distance().absoluteValue
-        }
-
-    /** Whether [TransitionState.Transition.finish] was called on this transition. */
-    var isFinishing = false
-        private set
-
-    /**
-     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
-     * or to the left of [toScene].
-     *
-     * Note that this distance can be equal to [DistanceUnspecified] during the first frame of a
-     * transition when the distance depends on the size or position of an element that is composed
-     * in the scene we are going to.
-     */
-    fun distance(): Float {
-        if (lastDistance != DistanceUnspecified) {
-            return lastDistance
-        }
-
-        val absoluteDistance =
-            with(transformationSpec.distance ?: DefaultSwipeDistance) {
-                userActionDistanceScope.absoluteDistance(
-                    _fromScene.targetSize,
-                    orientation,
-                )
-            }
-
-        if (absoluteDistance <= 0f) {
-            return DistanceUnspecified
-        }
-
-        val distance = if (isUpOrLeft) -absoluteDistance else absoluteDistance
-        lastDistance = distance
-        return distance
-    }
-
-    /** Ends any previous [offsetAnimation] and runs the new [animation]. */
-    private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
-        cancelOffsetAnimation()
-        return animation().also { offsetAnimation = it }
-    }
-
-    /** Cancel any ongoing offset animation. */
-    // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
-    // the same time.
-    fun cancelOffsetAnimation() {
-        val animation = offsetAnimation ?: return
-        offsetAnimation = null
-
-        dragOffset = animation.animatable.value
-        animation.job.cancel()
-    }
-
-    fun animateOffset(
-        // TODO(b/317063114) The CoroutineScope should be removed.
-        coroutineScope: CoroutineScope,
-        initialVelocity: Float,
-        targetOffset: Float,
-        targetScene: SceneKey,
-    ): OffsetAnimation {
-        val initialProgress = progress
-        // Skip the animation if we have already reached the target scene and the overscroll does
-        // not animate anything.
-        val hasReachedTargetScene =
-            (targetScene == toScene && initialProgress >= 1f) ||
-                (targetScene == fromScene && initialProgress <= 0f)
-        val skipAnimation = hasReachedTargetScene && !isWithinProgressRange(initialProgress)
-
-        return startOffsetAnimation {
-            val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
-            val isTargetGreater = targetOffset > animatable.value
-            val startedWhenOvercrollingTargetScene =
-                if (targetScene == fromScene) initialProgress < 0f else initialProgress > 1f
-            val job =
-                coroutineScope
-                    // Important: We start atomically to make sure that we start the coroutine even
-                    // if it is cancelled right after it is launched, so that snapToScene() is
-                    // correctly called. Otherwise, this transition will never be stopped and we
-                    // will never settle to Idle.
-                    .launch(start = CoroutineStart.ATOMIC) {
-                        // TODO(b/327249191): Refactor the code so that we don't even launch a
-                        // coroutine if we don't need to animate.
-                        if (skipAnimation) {
-                            snapToScene(targetScene)
-                            cancelOffsetAnimation()
-                            dragOffset = targetOffset
-                            return@launch
-                        }
-
-                        try {
-                            val swipeSpec =
-                                transformationSpec.swipeSpec
-                                    ?: layoutState.transitions.defaultSwipeSpec
-                            animatable.animateTo(
-                                targetValue = targetOffset,
-                                animationSpec = swipeSpec,
-                                initialVelocity = initialVelocity,
-                            ) {
-                                if (bouncingContent == null) {
-                                    val isBouncing =
-                                        if (isTargetGreater) {
-                                            if (startedWhenOvercrollingTargetScene) {
-                                                value >= targetOffset
-                                            } else {
-                                                value > targetOffset
-                                            }
-                                        } else {
-                                            if (startedWhenOvercrollingTargetScene) {
-                                                value <= targetOffset
-                                            } else {
-                                                value < targetOffset
-                                            }
-                                        }
-
-                                    if (isBouncing) {
-                                        bouncingContent = targetScene
-
-                                        // Immediately stop this transition if we are bouncing on a
-                                        // scene that does not bounce.
-                                        if (!isWithinProgressRange(progress)) {
-                                            snapToScene(targetScene)
-                                        }
-                                    }
-                                }
-                            }
-                        } finally {
-                            snapToScene(targetScene)
-                        }
-                    }
-
-            OffsetAnimation(animatable, job)
-        }
-    }
-
-    fun snapToScene(scene: SceneKey) {
-        cancelOffsetAnimation()
-        check(currentScene == scene)
-        layoutState.finishTransition(this)
-    }
-
-    override fun finish(): Job {
-        if (isFinishing) return requireNotNull(offsetAnimation).job
-        isFinishing = true
-
-        // If we were already animating the offset, simply return the job.
-        offsetAnimation?.let {
-            return it.job
-        }
-
-        // Animate to the current scene.
-        val targetScene = currentScene
-        val targetOffset =
-            if (targetScene == fromScene) {
-                0f
-            } else {
-                val distance = distance()
-                check(distance != DistanceUnspecified) {
-                    "targetScene != fromScene but distance is unspecified"
-                }
-                distance
-            }
-
-        val animation =
-            animateOffset(
-                coroutineScope = coroutineScope,
-                initialVelocity = 0f,
-                targetOffset = targetOffset,
-                targetScene = currentScene,
-            )
-        check(offsetAnimation == animation)
-        return animation.job
-    }
-
-    internal class OffsetAnimation(
-        /** The animatable used to animate the offset. */
-        val animatable: Animatable<Float, AnimationVector1D>,
-
-        /** The job in which [animatable] is animated. */
-        val job: Job,
-    )
-}
-
-private object DefaultSwipeDistance : UserActionDistance {
-    override fun UserActionDistanceScope.absoluteDistance(
-        fromSceneSize: IntSize,
-        orientation: Orientation,
-    ): Float {
-        return when (orientation) {
-            Orientation.Horizontal -> fromSceneSize.width
-            Orientation.Vertical -> fromSceneSize.height
-        }.toFloat()
-    }
-}
-
 /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-private class Swipes(
+internal class Swipes(
     val upOrLeft: Swipe.Resolved?,
     val downOrRight: Swipe.Resolved?,
     val upOrLeftNoSource: Swipe.Resolved?,
@@ -845,8 +514,8 @@
     var upOrLeftResult: UserActionResult? = null
     var downOrRightResult: UserActionResult? = null
 
-    fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
-        val userActions = fromScene.userActions
+    fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
+        val userActions = fromContent.userActions
         fun result(swipe: Swipe.Resolved?): UserActionResult? {
             return userActions[swipe ?: return null]
         }
@@ -856,39 +525,33 @@
         return upOrLeftResult to downOrRightResult
     }
 
-    fun updateSwipesResults(fromScene: Scene) {
-        val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene)
+    /**
+     * Update the swipes results.
+     *
+     * Usually we don't want to update them while doing a drag, because this could change the target
+     * content (jump cutting) to a different content, when some system state changed the targets the
+     * background. However, an update is needed any time we calculate the targets for a new
+     * fromContent.
+     */
+    fun updateSwipesResults(fromContent: Content) {
+        val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromContent)
 
         this.upOrLeftResult = upOrLeftResult
         this.downOrRightResult = downOrRightResult
     }
 
     /**
-     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
+     * Returns the [UserActionResult] in the direction of [directionOffset].
      *
-     * @param fromScene the scene from which we look for the target
      * @param directionOffset signed float that indicates the direction. Positive is down or right
      *   negative is up or left.
-     * @param updateSwipesResults whether the target scenes should be updated to the current values
-     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
-     *   this could change the target scene (jump cutting) to a different scene, when some system
-     *   state changed the targets the background. However, an update is needed any time we
-     *   calculate the targets for a new fromScene.
      * @return null when there are no targets in either direction. If one direction is null and you
      *   drag into the null direction this function will return the opposite direction, assuming
      *   that the users intention is to start the drag into the other direction eventually. If
      *   [directionOffset] is 0f and both direction are available, it will default to
      *   [upOrLeftResult].
      */
-    fun findUserActionResult(
-        fromScene: Scene,
-        directionOffset: Float,
-        updateSwipesResults: Boolean,
-    ): UserActionResult? {
-        if (updateSwipesResults) {
-            updateSwipesResults(fromScene)
-        }
-
+    fun findUserActionResult(directionOffset: Float): UserActionResult? {
         return when {
             upOrLeftResult == null && downOrRightResult == null -> null
             (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
@@ -896,18 +559,6 @@
             else -> downOrRightResult
         }
     }
-
-    /**
-     * A strict version of [findUserActionResult] that will return null when there is no Scene in
-     * [directionOffset] direction
-     */
-    fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
-        return when {
-            directionOffset > 0f -> upOrLeftResult
-            directionOffset < 0f -> downOrRightResult
-            else -> null
-        }
-    }
 }
 
 internal class NestedScrollHandlerImpl(
@@ -1085,7 +736,7 @@
                 val controller = dragController ?: error("Should be called after onStart")
 
                 controller
-                    .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
+                    .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
                     .also { dragController = null }
             },
         )
@@ -1103,5 +754,5 @@
 private object NoOpDragController : DragController {
     override fun onDrag(delta: Float) = 0f
 
-    override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f
+    override fun onStop(velocity: Float, canChangeContent: Boolean) = 0f
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
index 6181cfb..cb18c67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt
@@ -37,7 +37,7 @@
      * @see InterruptionResult
      */
     fun onInterruption(
-        interrupted: TransitionState.Transition.ChangeCurrentScene,
+        interrupted: TransitionState.Transition.ChangeScene,
         newTargetScene: SceneKey,
     ): InterruptionResult?
 }
@@ -76,7 +76,7 @@
  */
 object DefaultInterruptionHandler : InterruptionHandler {
     override fun onInterruption(
-        interrupted: TransitionState.Transition.ChangeCurrentScene,
+        interrupted: TransitionState.Transition.ChangeScene,
         newTargetScene: SceneKey,
     ): InterruptionResult {
         return InterruptionResult(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3487730..fd4c310 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -269,13 +269,13 @@
                                                     velocityTracker.calculateVelocity(maxVelocity)
                                                 }
                                                 .toFloat(),
-                                        onFling = { controller.onStop(it, canChangeScene = true) }
+                                        onFling = { controller.onStop(it, canChangeContent = true) }
                                     )
                                 },
                                 onDragCancel = { controller ->
                                     startFlingGesture(
                                         initialVelocity = 0f,
-                                        onFling = { controller.onStop(it, canChangeScene = true) }
+                                        onFling = { controller.onStop(it, canChangeContent = true) }
                                     )
                                 },
                                 swipeDetector = swipeDetector,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 236e202..3a7c2bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -43,7 +43,7 @@
     fun currentScene(): Flow<SceneKey> {
         return when (this) {
             is Idle -> flowOf(currentScene)
-            is Transition.ChangeCurrentScene -> currentScene
+            is Transition.ChangeScene -> currentScene
             is Transition.ShowOrHideOverlay -> flowOf(currentScene)
             is Transition.ReplaceOverlay -> flowOf(currentScene)
         }
@@ -94,7 +94,7 @@
                 .trimMargin()
 
         /** A transition animating between [fromScene] and [toScene]. */
-        class ChangeCurrentScene(
+        class ChangeScene(
             override val fromScene: SceneKey,
             override val toScene: SceneKey,
             val currentScene: Flow<SceneKey>,
@@ -174,8 +174,8 @@
                 previewProgress: Flow<Float> = flowOf(0f),
                 isInPreviewStage: Flow<Boolean> = flowOf(false),
                 currentOverlays: Flow<Set<OverlayKey>> = flowOf(emptySet()),
-            ): ChangeCurrentScene {
-                return ChangeCurrentScene(
+            ): ChangeScene {
+                return ChangeScene(
                     fromScene,
                     toScene,
                     currentScene,
@@ -190,7 +190,7 @@
         }
     }
 
-    fun isIdle(scene: SceneKey?): Boolean {
+    fun isIdle(scene: SceneKey? = null): Boolean {
         return this is Idle && (scene == null || this.currentScene == scene)
     }
 
@@ -210,8 +210,8 @@
     return snapshotFlow {
             when (val state = transitionState) {
                 is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
-                is TransitionState.Transition.ChangeCurrentScene -> {
-                    ObservableTransitionState.Transition.ChangeCurrentScene(
+                is TransitionState.Transition.ChangeScene -> {
+                    ObservableTransitionState.Transition.ChangeScene(
                         fromScene = state.fromScene,
                         toScene = state.toScene,
                         currentScene = snapshotFlow { state.currentScene },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index e7e6b2a..be4fea1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -18,120 +18,75 @@
 
 import androidx.activity.BackEventCompat
 import androidx.activity.compose.PredictiveBackHandler
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import com.android.compose.animation.scene.content.state.TransitionState
 import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
 
 @Composable
 internal fun PredictiveBackHandler(
-    state: MutableSceneTransitionLayoutStateImpl,
-    coroutineScope: CoroutineScope,
-    targetSceneForBack: SceneKey? = null,
+    layoutImpl: SceneTransitionLayoutImpl,
+    result: UserActionResult?,
 ) {
     PredictiveBackHandler(
-        enabled = targetSceneForBack != null,
+        enabled = result != null,
     ) { progress: Flow<BackEventCompat> ->
-        val fromScene = state.transitionState.currentScene
-        if (targetSceneForBack == null || targetSceneForBack == fromScene) {
+        if (result == null) {
             // Note: We have to collect progress otherwise PredictiveBackHandler will throw.
             progress.first()
             return@PredictiveBackHandler
         }
 
-        val transition =
-            PredictiveBackTransition(state, coroutineScope, fromScene, toScene = targetSceneForBack)
-        state.startTransition(transition)
-        try {
-            progress.collect { backEvent -> transition.dragProgress = backEvent.progress }
+        val animation =
+            createSwipeAnimation(
+                layoutImpl,
+                layoutImpl.coroutineScope,
+                result,
+                isUpOrLeft = false,
+                // Note that the orientation does not matter here given that it's only used to
+                // compute the distance. In our case the distance is always 1f.
+                orientation = Orientation.Horizontal,
+                distance = 1f,
+            )
 
-            // Back gesture successful.
-            transition.animateTo(targetSceneForBack)
-        } catch (e: CancellationException) {
-            // Back gesture cancelled.
-            transition.animateTo(fromScene)
-        }
+        animate(layoutImpl, animation, progress)
     }
 }
 
-private class PredictiveBackTransition(
-    val state: MutableSceneTransitionLayoutStateImpl,
-    val coroutineScope: CoroutineScope,
-    fromScene: SceneKey,
-    toScene: SceneKey,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene) {
-    override var currentScene by mutableStateOf(fromScene)
-        private set
-
-    /** The animated progress once the gesture was committed or cancelled. */
-    private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null)
-    var dragProgress: Float by mutableFloatStateOf(0f)
-
-    override val previewProgress: Float
-        get() = dragProgress
-
-    override val previewProgressVelocity: Float
-        get() = 0f // Currently, velocity is not exposed by predictive back API
-
-    override val isInPreviewStage: Boolean
-        get() = previewTransformationSpec != null && currentScene == fromScene
-
-    override val progress: Float
-        get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress
-
-    override val progressVelocity: Float
-        get() = progressAnimatable?.velocity ?: 0f
-
-    override val isInitiatedByUserInput: Boolean
-        get() = true
-
-    override val isUserInputOngoing: Boolean
-        get() = progressAnimatable == null
-
-    private var animationJob: Job? = null
-
-    override fun finish(): Job = animateTo(currentScene)
-
-    fun animateTo(scene: SceneKey): Job {
-        check(scene == fromScene || scene == toScene)
-        animationJob?.let {
-            return it
+private suspend fun <T : ContentKey> animate(
+    layoutImpl: SceneTransitionLayoutImpl,
+    animation: SwipeAnimation<T>,
+    progress: Flow<BackEventCompat>,
+) {
+    fun animateOffset(targetContent: T) {
+        if (
+            layoutImpl.state.transitionState != animation.contentTransition || animation.isFinishing
+        ) {
+            return
         }
 
-        if (scene != currentScene && state.transitionState == this && state.canChangeScene(scene)) {
-            currentScene = scene
-        }
+        animation.animateOffset(
+            initialVelocity = 0f,
+            targetContent = targetContent,
 
-        val targetProgress =
-            when (currentScene) {
-                fromScene -> 0f
-                toScene -> 1f
-                else -> error("scene $currentScene should be either $fromScene or $toScene")
-            }
-        val startProgress = if (previewTransformationSpec != null) 0f else dragProgress
-        val animatable = Animatable(startProgress).also { progressAnimatable = it }
+            // TODO(b/350705972): Allow to customize or reuse the same customization endpoints as
+            // the normal swipe transitions. We can't just reuse them here because other swipe
+            // transitions animate pixels while this transition animates progress, so the visibility
+            // thresholds will be completely different.
+            spec = spring(),
+        )
+    }
 
-        // Important: We start atomically to make sure that we start the coroutine even if it is
-        // cancelled right after it is launched, so that finishTransition() is correctly called.
-        return coroutineScope
-            .launch(start = CoroutineStart.ATOMIC) {
-                try {
-                    animatable.animateTo(targetProgress)
-                } finally {
-                    state.finishTransition(this@PredictiveBackTransition)
-                }
-            }
-            .also { animationJob = it }
+    layoutImpl.state.startTransition(animation.contentTransition)
+    try {
+        progress.collect { backEvent -> animation.dragOffset = backEvent.progress }
+
+        // Back gesture successful.
+        animateOffset(animation.toContent)
+    } catch (e: CancellationException) {
+        // Back gesture cancelled.
+        animateOffset(animation.fromContent)
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index aaa2546..b3f74f7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -104,10 +104,10 @@
      * call order. Calling overlay(A) followed by overlay(B) will mean that overlay B renders
      * after/above overlay A.
      */
-    // TODO(b/353679003): Allow to specify user actions. When overlays are shown, the user actions
-    // of the top-most overlay in currentOverlays will be used.
     fun overlay(
         key: OverlayKey,
+        userActions: Map<UserAction, UserActionResult> =
+            mapOf(Back to UserActionResult.HideOverlay(key)),
         alignment: Alignment = Alignment.Center,
         content: @Composable ContentScope.() -> Unit,
     )
@@ -479,20 +479,79 @@
 }
 
 /** The result of performing a [UserAction]. */
-data class UserActionResult(
-    /** The scene we should be transitioning to during the [UserAction]. */
-    val toScene: SceneKey,
-
+sealed class UserActionResult(
     /** The key of the transition that should be used. */
-    val transitionKey: TransitionKey? = null,
+    open val transitionKey: TransitionKey? = null,
 
     /**
      * If `true`, the swipe will be committed and we will settle to [toScene] if only if the user
      * swiped at least the swipe distance, i.e. the transition progress was already equal to or
      * bigger than 100% when the user released their finger. `
      */
-    val requiresFullDistanceSwipe: Boolean = false,
-)
+    open val requiresFullDistanceSwipe: Boolean,
+) {
+    internal abstract fun toContent(currentScene: SceneKey): ContentKey
+
+    data class ChangeScene
+    internal constructor(
+        /** The scene we should be transitioning to during the [UserAction]. */
+        val toScene: SceneKey,
+        override val transitionKey: TransitionKey? = null,
+        override val requiresFullDistanceSwipe: Boolean = false,
+    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
+        override fun toContent(currentScene: SceneKey): ContentKey = toScene
+    }
+
+    /** A [UserActionResult] that shows [overlay]. */
+    class ShowOverlay(
+        val overlay: OverlayKey,
+        transitionKey: TransitionKey? = null,
+        requiresFullDistanceSwipe: Boolean = false,
+    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
+        override fun toContent(currentScene: SceneKey): ContentKey = overlay
+    }
+
+    /** A [UserActionResult] that hides [overlay]. */
+    class HideOverlay(
+        val overlay: OverlayKey,
+        transitionKey: TransitionKey? = null,
+        requiresFullDistanceSwipe: Boolean = false,
+    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
+        override fun toContent(currentScene: SceneKey): ContentKey = currentScene
+    }
+
+    /**
+     * A [UserActionResult] that replaces the current overlay by [overlay].
+     *
+     * Note: This result can only be used for user actions of overlays and an exception will be
+     * thrown if it is used for a scene.
+     */
+    class ReplaceByOverlay(
+        val overlay: OverlayKey,
+        transitionKey: TransitionKey? = null,
+        requiresFullDistanceSwipe: Boolean = false,
+    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
+        override fun toContent(currentScene: SceneKey): ContentKey = overlay
+    }
+
+    companion object {
+        /** A [UserActionResult] that changes the current scene to [toScene]. */
+        operator fun invoke(
+            /** The scene we should be transitioning to during the [UserAction]. */
+            toScene: SceneKey,
+
+            /** The key of the transition that should be used. */
+            transitionKey: TransitionKey? = null,
+
+            /**
+             * If `true`, the swipe will be committed if only if the user swiped at least the swipe
+             * distance, i.e. the transition progress was already equal to or bigger than 100% when
+             * the user released their finger.
+             */
+            requiresFullDistanceSwipe: Boolean = false,
+        ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
+    }
+}
 
 fun interface UserActionDistance {
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 5f5141e..b33b4f6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -31,6 +32,7 @@
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -132,6 +134,8 @@
     internal lateinit var lookaheadScope: LookaheadScope
         private set
 
+    internal var lastSize: IntSize = IntSize.Zero
+
     init {
         updateContents(builder, layoutDirection)
 
@@ -179,6 +183,28 @@
         }
     }
 
+    internal fun contentForUserActions(): Content {
+        return findOverlayWithHighestZIndex() ?: scene(state.transitionState.currentScene)
+    }
+
+    private fun findOverlayWithHighestZIndex(): Overlay? {
+        val currentOverlays = state.transitionState.currentOverlays
+        if (currentOverlays.isEmpty()) {
+            return null
+        }
+
+        var overlay: Overlay? = null
+        currentOverlays.forEach { key ->
+            val previousZIndex = overlay?.zIndex
+            val candidate = overlay(key)
+            if (previousZIndex == null || candidate.zIndex > previousZIndex) {
+                overlay = candidate
+            }
+        }
+
+        return overlay
+    }
+
     internal fun updateContents(
         builder: SceneTransitionLayoutScope.() -> Unit,
         layoutDirection: LayoutDirection,
@@ -203,8 +229,7 @@
 
                     scenesToRemove.remove(key)
 
-                    val resolvedUserActions =
-                        userActions.mapKeys { it.key.resolve(layoutDirection) }
+                    val resolvedUserActions = resolveUserActions(key, userActions, layoutDirection)
                     val scene = scenes[key]
                     if (scene != null) {
                         // Update an existing scene.
@@ -228,6 +253,7 @@
 
                 override fun overlay(
                     key: OverlayKey,
+                    userActions: Map<UserAction, UserActionResult>,
                     alignment: Alignment,
                     content: @Composable (ContentScope.() -> Unit)
                 ) {
@@ -235,10 +261,12 @@
                     overlaysToRemove.remove(key)
 
                     val overlay = overlays[key]
+                    val resolvedUserActions = resolveUserActions(key, userActions, layoutDirection)
                     if (overlay != null) {
                         // Update an existing overlay.
                         overlay.content = content
                         overlay.zIndex = zIndex
+                        overlay.userActions = resolvedUserActions
                         overlay.alignment = alignment
                     } else {
                         // New overlay.
@@ -247,8 +275,7 @@
                                 key,
                                 this@SceneTransitionLayoutImpl,
                                 content,
-                                // TODO(b/353679003): Allow to specify user actions
-                                actions = emptyMap(),
+                                resolvedUserActions,
                                 zIndex,
                                 alignment,
                             )
@@ -263,6 +290,46 @@
         overlaysToRemove.forEach { overlays.remove(it) }
     }
 
+    private fun resolveUserActions(
+        key: ContentKey,
+        userActions: Map<UserAction, UserActionResult>,
+        layoutDirection: LayoutDirection
+    ): Map<UserAction.Resolved, UserActionResult> {
+        return userActions
+            .mapKeys { it.key.resolve(layoutDirection) }
+            .also { checkUserActions(key, it) }
+    }
+
+    private fun checkUserActions(
+        key: ContentKey,
+        userActions: Map<UserAction.Resolved, UserActionResult>,
+    ) {
+        userActions.forEach { (action, result) ->
+            fun details() = "Content $key, action $action, result $result."
+
+            when (result) {
+                is UserActionResult.ChangeScene -> {
+                    check(key != result.toScene) {
+                        error("Transition to the same scene is not supported. ${details()}")
+                    }
+                }
+                is UserActionResult.ReplaceByOverlay -> {
+                    check(key is OverlayKey) {
+                        "ReplaceByOverlay() can only be used for overlays, not scenes. ${details()}"
+                    }
+
+                    check(key != result.overlay) {
+                        "Transition to the same overlay is not supported. ${details()}"
+                    }
+                }
+                is UserActionResult.ShowOverlay,
+                is UserActionResult.HideOverlay -> {
+                    /* Always valid. */
+                }
+            }
+        }
+    }
+
     @Composable
     internal fun Content(modifier: Modifier, swipeDetector: SwipeDetector) {
         Box(
@@ -286,9 +353,8 @@
 
     @Composable
     private fun BackHandler() {
-        val targetSceneForBack =
-            scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
-        PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
+        val result = contentForUserActions().userActions[Back.Resolved]
+        PredictiveBackHandler(layoutImpl = this, result = result)
     }
 
     @Composable
@@ -312,7 +378,7 @@
                 // Compose the new scene we are going to first.
                 transitions.fastForEachReversed { transition ->
                     when (transition) {
-                        is TransitionState.Transition.ChangeCurrentScene -> {
+                        is TransitionState.Transition.ChangeScene -> {
                             maybeAdd(transition.toScene)
                             maybeAdd(transition.fromScene)
                         }
@@ -362,7 +428,7 @@
 
                     transitions.fastForEach { transition ->
                         when (transition) {
-                            is TransitionState.Transition.ChangeCurrentScene -> {}
+                            is TransitionState.Transition.ChangeScene -> {}
                             is TransitionState.Transition.ShowOrHideOverlay ->
                                 maybeAdd(transition.overlay)
                             is TransitionState.Transition.ReplaceOverlay -> {
@@ -379,8 +445,10 @@
             .sortedBy { it.zIndex }
     }
 
-    internal fun setScenesTargetSizeForTest(size: IntSize) {
-        scenes.values.forEach { it.targetSize = size }
+    @VisibleForTesting
+    internal fun setContentsAndLayoutTargetSizeForTest(size: IntSize) {
+        lastSize = size
+        (scenes.values + overlays.values).forEach { it.targetSize = size }
     }
 
     internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays
@@ -396,7 +464,11 @@
 }
 
 private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
-    Modifier.Node(), ApproachLayoutModifierNode {
+    Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode {
+    override fun onRemeasured(size: IntSize) {
+        layoutImpl.lastSize = size
+    }
+
     override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean {
         return layoutImpl.state.isTransitioning()
     }
@@ -412,7 +484,7 @@
         val width: Int
         val height: Int
         val transition =
-            layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeCurrentScene
+            layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene
         if (transition == null) {
             width = placeable.width
             height = placeable.height
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0ac6912..f3128f1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -183,6 +183,12 @@
  *   commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
  *   `true`, then the gesture will be committed and we will animate to the other scene. Otherwise,
  *   the gesture will be cancelled and we will animate back to the current scene.
+ * @param canShowOverlay whether we should commit a user action that will result in showing the
+ *   given overlay.
+ * @param canHideOverlay whether we should commit a user action that will result in hiding the given
+ *   overlay.
+ * @param canReplaceOverlay whether we should commit a user action that will result in replacing
+ *   `from` overlay by `to` overlay.
  * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
  *   [SceneTransitionLayoutState]s.
  */
@@ -191,6 +197,9 @@
     transitions: SceneTransitions = SceneTransitions.Empty,
     initialOverlays: Set<OverlayKey> = emptySet(),
     canChangeScene: (SceneKey) -> Boolean = { true },
+    canShowOverlay: (OverlayKey) -> Boolean = { true },
+    canHideOverlay: (OverlayKey) -> Boolean = { true },
+    canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
     stateLinks: List<StateLink> = emptyList(),
     enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
 ): MutableSceneTransitionLayoutState {
@@ -199,6 +208,9 @@
         transitions,
         initialOverlays,
         canChangeScene,
+        canShowOverlay,
+        canHideOverlay,
+        canReplaceOverlay,
         stateLinks,
         enableInterruptions,
     )
@@ -210,6 +222,11 @@
     override var transitions: SceneTransitions = transitions {},
     initialOverlays: Set<OverlayKey> = emptySet(),
     internal val canChangeScene: (SceneKey) -> Boolean = { true },
+    internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
+    internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
+    internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+        true
+    },
     private val stateLinks: List<StateLink> = emptyList(),
 
     // TODO(b/290930950): Remove this flag.
@@ -282,7 +299,7 @@
         targetScene: SceneKey,
         coroutineScope: CoroutineScope,
         transitionKey: TransitionKey?,
-    ): TransitionState.Transition.ChangeCurrentScene? {
+    ): TransitionState.Transition.ChangeScene? {
         checkThread()
 
         return coroutineScope.animateToScene(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
new file mode 100644
index 0000000..57ff597
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+internal fun createSwipeAnimation(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    animationScope: CoroutineScope,
+    result: UserActionResult,
+    isUpOrLeft: Boolean,
+    orientation: Orientation,
+    distance: Float,
+): SwipeAnimation<*> {
+    return createSwipeAnimation(
+        layoutState,
+        animationScope,
+        result,
+        isUpOrLeft,
+        orientation,
+        distance = { distance },
+        contentForUserActions = {
+            error("Computing contentForUserActions requires a SceneTransitionLayoutImpl")
+        },
+    )
+}
+
+internal fun createSwipeAnimation(
+    layoutImpl: SceneTransitionLayoutImpl,
+    animationScope: CoroutineScope,
+    result: UserActionResult,
+    isUpOrLeft: Boolean,
+    orientation: Orientation,
+    distance: Float = DistanceUnspecified
+): SwipeAnimation<*> {
+    var lastDistance = distance
+
+    fun distance(animation: SwipeAnimation<*>): Float {
+        if (lastDistance != DistanceUnspecified) {
+            return lastDistance
+        }
+
+        val absoluteDistance =
+            with(animation.contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) {
+                layoutImpl.userActionDistanceScope.absoluteDistance(
+                    layoutImpl.content(animation.fromContent).targetSize,
+                    orientation,
+                )
+            }
+
+        if (absoluteDistance <= 0f) {
+            return DistanceUnspecified
+        }
+
+        val distance = if (isUpOrLeft) -absoluteDistance else absoluteDistance
+        lastDistance = distance
+        return distance
+    }
+
+    return createSwipeAnimation(
+        layoutImpl.state,
+        animationScope,
+        result,
+        isUpOrLeft,
+        orientation,
+        distance = ::distance,
+        contentForUserActions = { layoutImpl.contentForUserActions().key },
+    )
+}
+
+private fun createSwipeAnimation(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    animationScope: CoroutineScope,
+    result: UserActionResult,
+    isUpOrLeft: Boolean,
+    orientation: Orientation,
+    distance: (SwipeAnimation<*>) -> Float,
+    contentForUserActions: () -> ContentKey,
+): SwipeAnimation<*> {
+    fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
+        return SwipeAnimation(
+            layoutState = layoutState,
+            animationScope = animationScope,
+            fromContent = fromContent,
+            toContent = toContent,
+            orientation = orientation,
+            isUpOrLeft = isUpOrLeft,
+            requiresFullDistanceSwipe = result.requiresFullDistanceSwipe,
+            distance = distance,
+        )
+    }
+
+    return when (result) {
+        is UserActionResult.ChangeScene -> {
+            val fromScene = layoutState.currentScene
+            val toScene = result.toScene
+            ChangeSceneSwipeTransition(
+                    layoutState = layoutState,
+                    swipeAnimation = swipeAnimation(fromContent = fromScene, toContent = toScene),
+                    key = result.transitionKey,
+                    replacedTransition = null,
+                )
+                .swipeAnimation
+        }
+        is UserActionResult.ShowOverlay -> {
+            val fromScene = layoutState.currentScene
+            val overlay = result.overlay
+            ShowOrHideOverlaySwipeTransition(
+                    layoutState = layoutState,
+                    fromOrToScene = fromScene,
+                    overlay = overlay,
+                    swipeAnimation = swipeAnimation(fromContent = fromScene, toContent = overlay),
+                    key = result.transitionKey,
+                    replacedTransition = null,
+                )
+                .swipeAnimation
+        }
+        is UserActionResult.HideOverlay -> {
+            val toScene = layoutState.currentScene
+            val overlay = result.overlay
+            ShowOrHideOverlaySwipeTransition(
+                    layoutState = layoutState,
+                    fromOrToScene = toScene,
+                    overlay = overlay,
+                    swipeAnimation = swipeAnimation(fromContent = overlay, toContent = toScene),
+                    key = result.transitionKey,
+                    replacedTransition = null,
+                )
+                .swipeAnimation
+        }
+        is UserActionResult.ReplaceByOverlay -> {
+            val fromOverlay =
+                when (val contentForUserActions = contentForUserActions()) {
+                    is SceneKey ->
+                        error("ReplaceByOverlay can only be called when an overlay is shown")
+                    is OverlayKey -> contentForUserActions
+                }
+
+            val toOverlay = result.overlay
+            ReplaceOverlaySwipeTransition(
+                    layoutState = layoutState,
+                    swipeAnimation =
+                        swipeAnimation(fromContent = fromOverlay, toContent = toOverlay),
+                    key = result.transitionKey,
+                    replacedTransition = null,
+                )
+                .swipeAnimation
+        }
+    }
+}
+
+internal fun createSwipeAnimation(old: SwipeAnimation<*>): SwipeAnimation<*> {
+    return when (val transition = old.contentTransition) {
+        is TransitionState.Transition.ChangeScene -> {
+            ChangeSceneSwipeTransition(transition as ChangeSceneSwipeTransition).swipeAnimation
+        }
+        is TransitionState.Transition.ShowOrHideOverlay -> {
+            ShowOrHideOverlaySwipeTransition(transition as ShowOrHideOverlaySwipeTransition)
+                .swipeAnimation
+        }
+        is TransitionState.Transition.ReplaceOverlay -> {
+            ReplaceOverlaySwipeTransition(transition as ReplaceOverlaySwipeTransition)
+                .swipeAnimation
+        }
+    }
+}
+
+/** A helper class that contains the main logic for swipe transitions. */
+internal class SwipeAnimation<T : ContentKey>(
+    val layoutState: MutableSceneTransitionLayoutStateImpl,
+    val animationScope: CoroutineScope,
+    val fromContent: T,
+    val toContent: T,
+    override val orientation: Orientation,
+    override val isUpOrLeft: Boolean,
+    val requiresFullDistanceSwipe: Boolean,
+    private val distance: (SwipeAnimation<T>) -> Float,
+    currentContent: T = fromContent,
+    dragOffset: Float = 0f,
+) : TransitionState.HasOverscrollProperties {
+    /** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */
+    lateinit var contentTransition: TransitionState.Transition
+
+    var currentContent by mutableStateOf(currentContent)
+
+    val progress: Float
+        get() {
+            // Important: If we are going to return early because distance is equal to 0, we should
+            // still make sure we read the offset before returning so that the calling code still
+            // subscribes to the offset value.
+            val animatable = offsetAnimation?.animatable
+            val offset =
+                when {
+                    animatable != null -> animatable.value
+                    contentTransition.previewTransformationSpec != null -> 0f
+                    else -> dragOffset
+                }
+
+            return computeProgress(offset)
+        }
+
+    fun computeProgress(offset: Float): Float {
+        val distance = distance()
+        if (distance == DistanceUnspecified) {
+            return 0f
+        }
+        return offset / distance
+    }
+
+    val progressVelocity: Float
+        get() {
+            val animatable = offsetAnimation?.animatable ?: return 0f
+            val distance = distance()
+            if (distance == DistanceUnspecified) {
+                return 0f
+            }
+
+            val velocityInDistanceUnit = animatable.velocity
+            return velocityInDistanceUnit / distance.absoluteValue
+        }
+
+    val previewProgress: Float
+        get() = computeProgress(dragOffset)
+
+    val previewProgressVelocity: Float
+        get() = 0f
+
+    val isInPreviewStage: Boolean
+        get() = contentTransition.previewTransformationSpec != null && currentContent == fromContent
+
+    override var bouncingContent: ContentKey? = null
+
+    /** The current offset caused by the drag gesture. */
+    var dragOffset by mutableFloatStateOf(dragOffset)
+
+    /** The offset animation that animates the offset once the user lifts their finger. */
+    private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
+
+    val isUserInputOngoing: Boolean
+        get() = offsetAnimation == null
+
+    override val absoluteDistance: Float
+        get() = distance().absoluteValue
+
+    /** Whether [finish] was called on this animation. */
+    var isFinishing = false
+        private set
+
+    constructor(
+        other: SwipeAnimation<T>
+    ) : this(
+        layoutState = other.layoutState,
+        animationScope = other.animationScope,
+        fromContent = other.fromContent,
+        toContent = other.toContent,
+        orientation = other.orientation,
+        isUpOrLeft = other.isUpOrLeft,
+        requiresFullDistanceSwipe = other.requiresFullDistanceSwipe,
+        distance = other.distance,
+        currentContent = other.currentContent,
+        dragOffset = other.dragOffset,
+    )
+
+    /**
+     * The signed distance between [fromContent] and [toContent]. It is negative if [fromContent] is
+     * above or to the left of [toContent].
+     *
+     * Note that this distance can be equal to [DistanceUnspecified] during the first frame of a
+     * transition when the distance depends on the size or position of an element that is composed
+     * in the content we are going to.
+     */
+    fun distance(): Float = distance(this)
+
+    /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+    private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
+        cancelOffsetAnimation()
+        return animation().also { offsetAnimation = it }
+    }
+
+    /** Cancel any ongoing offset animation. */
+    // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
+    // the same time.
+    fun cancelOffsetAnimation() {
+        val animation = offsetAnimation ?: return
+        offsetAnimation = null
+
+        dragOffset = animation.animatable.value
+        animation.job.cancel()
+    }
+
+    fun animateOffset(
+        initialVelocity: Float,
+        targetContent: T,
+        spec: SpringSpec<Float>? = null,
+    ): OffsetAnimation {
+        val initialProgress = progress
+        // Skip the animation if we have already reached the target content and the overscroll does
+        // not animate anything.
+        val hasReachedTargetContent =
+            (targetContent == toContent && initialProgress >= 1f) ||
+                (targetContent == fromContent && initialProgress <= 0f)
+        val skipAnimation =
+            hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress)
+
+        val targetContent =
+            if (targetContent != currentContent && !canChangeContent(targetContent)) {
+                currentContent
+            } else {
+                targetContent
+            }
+
+        val targetOffset =
+            if (targetContent == fromContent) {
+                0f
+            } else {
+                val distance = distance()
+                check(distance != DistanceUnspecified) {
+                    "distance is equal to $DistanceUnspecified"
+                }
+                distance
+            }
+
+        // If the effective current content changed, it should be reflected right now in the
+        // current state, even before the settle animation is ongoing. That way all the
+        // swipeables and back handlers will be refreshed and the user can for instance quickly
+        // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
+        // immediately go back B => A.
+        if (targetContent != currentContent) {
+            currentContent = targetContent
+        }
+
+        return startOffsetAnimation {
+            val startProgress =
+                if (contentTransition.previewTransformationSpec != null) 0f else dragOffset
+            val animatable = Animatable(startProgress, OffsetVisibilityThreshold)
+            val isTargetGreater = targetOffset > animatable.value
+            val startedWhenOvercrollingTargetContent =
+                if (targetContent == fromContent) initialProgress < 0f else initialProgress > 1f
+            val job =
+                animationScope
+                    // Important: We start atomically to make sure that we start the coroutine even
+                    // if it is cancelled right after it is launched, so that snapToContent() is
+                    // correctly called. Otherwise, this transition will never be stopped and we
+                    // will never settle to Idle.
+                    .launch(start = CoroutineStart.ATOMIC) {
+                        // TODO(b/327249191): Refactor the code so that we don't even launch a
+                        // coroutine if we don't need to animate.
+                        if (skipAnimation) {
+                            snapToContent(targetContent)
+                            dragOffset = targetOffset
+                            return@launch
+                        }
+
+                        try {
+                            val swipeSpec =
+                                spec
+                                    ?: contentTransition.transformationSpec.swipeSpec
+                                    ?: layoutState.transitions.defaultSwipeSpec
+                            animatable.animateTo(
+                                targetValue = targetOffset,
+                                animationSpec = swipeSpec,
+                                initialVelocity = initialVelocity,
+                            ) {
+                                if (bouncingContent == null) {
+                                    val isBouncing =
+                                        if (isTargetGreater) {
+                                            if (startedWhenOvercrollingTargetContent) {
+                                                value >= targetOffset
+                                            } else {
+                                                value > targetOffset
+                                            }
+                                        } else {
+                                            if (startedWhenOvercrollingTargetContent) {
+                                                value <= targetOffset
+                                            } else {
+                                                value < targetOffset
+                                            }
+                                        }
+
+                                    if (isBouncing) {
+                                        bouncingContent = targetContent
+
+                                        // Immediately stop this transition if we are bouncing on a
+                                        // content that does not bounce.
+                                        if (!contentTransition.isWithinProgressRange(progress)) {
+                                            snapToContent(targetContent)
+                                        }
+                                    }
+                                }
+                            }
+                        } finally {
+                            snapToContent(targetContent)
+                        }
+                    }
+
+            OffsetAnimation(animatable, job)
+        }
+    }
+
+    private fun canChangeContent(targetContent: ContentKey): Boolean {
+        return when (val transition = contentTransition) {
+            is TransitionState.Transition.ChangeScene ->
+                layoutState.canChangeScene(targetContent as SceneKey)
+            is TransitionState.Transition.ShowOrHideOverlay -> {
+                if (targetContent == transition.overlay) {
+                    layoutState.canShowOverlay(transition.overlay)
+                } else {
+                    layoutState.canHideOverlay(transition.overlay)
+                }
+            }
+            is TransitionState.Transition.ReplaceOverlay -> {
+                val to = targetContent as OverlayKey
+                val from =
+                    if (to == transition.toOverlay) transition.fromOverlay else transition.toOverlay
+                layoutState.canReplaceOverlay(from, to)
+            }
+        }
+    }
+
+    private fun snapToContent(content: T) {
+        cancelOffsetAnimation()
+        check(currentContent == content)
+        layoutState.finishTransition(contentTransition)
+    }
+
+    fun finish(): Job {
+        if (isFinishing) return requireNotNull(offsetAnimation).job
+        isFinishing = true
+
+        // If we were already animating the offset, simply return the job.
+        offsetAnimation?.let {
+            return it.job
+        }
+
+        // Animate to the current content.
+        val animation = animateOffset(initialVelocity = 0f, targetContent = currentContent)
+        check(offsetAnimation == animation)
+        return animation.job
+    }
+
+    internal class OffsetAnimation(
+        /** The animatable used to animate the offset. */
+        val animatable: Animatable<Float, AnimationVector1D>,
+
+        /** The job in which [animatable] is animated. */
+        val job: Job,
+    )
+}
+
+private object DefaultSwipeDistance : UserActionDistance {
+    override fun UserActionDistanceScope.absoluteDistance(
+        fromSceneSize: IntSize,
+        orientation: Orientation,
+    ): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> fromSceneSize.width
+            Orientation.Vertical -> fromSceneSize.height
+        }.toFloat()
+    }
+}
+
+private class ChangeSceneSwipeTransition(
+    val layoutState: MutableSceneTransitionLayoutStateImpl,
+    val swipeAnimation: SwipeAnimation<SceneKey>,
+    override val key: TransitionKey?,
+    replacedTransition: ChangeSceneSwipeTransition?,
+) :
+    TransitionState.Transition.ChangeScene(
+        swipeAnimation.fromContent,
+        swipeAnimation.toContent,
+        replacedTransition,
+    ),
+    TransitionState.HasOverscrollProperties by swipeAnimation {
+
+    constructor(
+        other: ChangeSceneSwipeTransition
+    ) : this(
+        layoutState = other.layoutState,
+        swipeAnimation = SwipeAnimation(other.swipeAnimation),
+        key = other.key,
+        replacedTransition = other,
+    )
+
+    init {
+        swipeAnimation.contentTransition = this
+    }
+
+    override val currentScene: SceneKey
+        get() = swipeAnimation.currentContent
+
+    override val progress: Float
+        get() = swipeAnimation.progress
+
+    override val progressVelocity: Float
+        get() = swipeAnimation.progressVelocity
+
+    override val previewProgress: Float
+        get() = swipeAnimation.previewProgress
+
+    override val previewProgressVelocity: Float
+        get() = swipeAnimation.previewProgressVelocity
+
+    override val isInPreviewStage: Boolean
+        get() = swipeAnimation.isInPreviewStage
+
+    override val isInitiatedByUserInput: Boolean = true
+
+    override val isUserInputOngoing: Boolean
+        get() = swipeAnimation.isUserInputOngoing
+
+    override fun finish(): Job = swipeAnimation.finish()
+}
+
+private class ShowOrHideOverlaySwipeTransition(
+    val layoutState: MutableSceneTransitionLayoutStateImpl,
+    val swipeAnimation: SwipeAnimation<ContentKey>,
+    overlay: OverlayKey,
+    fromOrToScene: SceneKey,
+    override val key: TransitionKey?,
+    replacedTransition: ShowOrHideOverlaySwipeTransition?,
+) :
+    TransitionState.Transition.ShowOrHideOverlay(
+        overlay,
+        fromOrToScene,
+        swipeAnimation.fromContent,
+        swipeAnimation.toContent,
+        replacedTransition,
+    ),
+    TransitionState.HasOverscrollProperties by swipeAnimation {
+    constructor(
+        other: ShowOrHideOverlaySwipeTransition
+    ) : this(
+        layoutState = other.layoutState,
+        swipeAnimation = SwipeAnimation(other.swipeAnimation),
+        overlay = other.overlay,
+        fromOrToScene = other.fromOrToScene,
+        key = other.key,
+        replacedTransition = other,
+    )
+
+    init {
+        swipeAnimation.contentTransition = this
+    }
+
+    override val isEffectivelyShown: Boolean
+        get() = swipeAnimation.currentContent == overlay
+
+    override val progress: Float
+        get() = swipeAnimation.progress
+
+    override val progressVelocity: Float
+        get() = swipeAnimation.progressVelocity
+
+    override val previewProgress: Float
+        get() = swipeAnimation.previewProgress
+
+    override val previewProgressVelocity: Float
+        get() = swipeAnimation.previewProgressVelocity
+
+    override val isInPreviewStage: Boolean
+        get() = swipeAnimation.isInPreviewStage
+
+    override val isInitiatedByUserInput: Boolean = true
+
+    override val isUserInputOngoing: Boolean
+        get() = swipeAnimation.isUserInputOngoing
+
+    override fun finish(): Job = swipeAnimation.finish()
+}
+
+private class ReplaceOverlaySwipeTransition(
+    val layoutState: MutableSceneTransitionLayoutStateImpl,
+    val swipeAnimation: SwipeAnimation<OverlayKey>,
+    override val key: TransitionKey?,
+    replacedTransition: ReplaceOverlaySwipeTransition?,
+) :
+    TransitionState.Transition.ReplaceOverlay(
+        swipeAnimation.fromContent,
+        swipeAnimation.toContent,
+        replacedTransition,
+    ),
+    TransitionState.HasOverscrollProperties by swipeAnimation {
+    constructor(
+        other: ReplaceOverlaySwipeTransition
+    ) : this(
+        layoutState = other.layoutState,
+        swipeAnimation = SwipeAnimation(other.swipeAnimation),
+        key = other.key,
+        replacedTransition = other,
+    )
+
+    init {
+        swipeAnimation.contentTransition = this
+    }
+
+    override val effectivelyShownOverlay: OverlayKey
+        get() = swipeAnimation.currentContent
+
+    override val progress: Float
+        get() = swipeAnimation.progress
+
+    override val progressVelocity: Float
+        get() = swipeAnimation.progressVelocity
+
+    override val previewProgress: Float
+        get() = swipeAnimation.previewProgress
+
+    override val previewProgressVelocity: Float
+        get() = swipeAnimation.previewProgressVelocity
+
+    override val isInPreviewStage: Boolean
+        get() = swipeAnimation.isInPreviewStage
+
+    override val isInitiatedByUserInput: Boolean = true
+
+    override val isUserInputOngoing: Boolean
+        get() = swipeAnimation.isUserInputOngoing
+
+    override fun finish(): Job = swipeAnimation.finish()
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index d1e83ba..dc7eda5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,7 +31,7 @@
 import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.unit.IntSize
-import com.android.compose.animation.scene.content.Scene
+import com.android.compose.animation.scene.content.Content
 
 /**
  * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
@@ -126,16 +126,15 @@
 
     private fun enabled(): Boolean {
         return draggableHandler.isDrivingTransition ||
-            currentScene().shouldEnableSwipes(multiPointerDraggableNode.orientation)
+            contentForSwipes().shouldEnableSwipes(multiPointerDraggableNode.orientation)
     }
 
-    private fun currentScene(): Scene {
-        val layoutImpl = draggableHandler.layoutImpl
-        return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+    private fun contentForSwipes(): Content {
+        return draggableHandler.layoutImpl.contentForUserActions()
     }
 
     /** Whether swipe should be enabled in the given [orientation]. */
-    private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+    private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
         return userActions.keys.any {
             it is Swipe.Resolved && it.direction.orientation == orientation
         }
@@ -153,7 +152,7 @@
                 Orientation.Vertical -> Orientation.Horizontal
                 Orientation.Horizontal -> Orientation.Vertical
             }
-        return currentScene().shouldEnableSwipes(oppositeOrientation)
+        return contentForSwipes().shouldEnableSwipes(oppositeOrientation)
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 6bc1754..59dd896 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -66,27 +66,7 @@
     var content by mutableStateOf(content)
     var zIndex by mutableFloatStateOf(zIndex)
     var targetSize by mutableStateOf(IntSize.Zero)
-
-    private var _userActions by mutableStateOf(checkValid(actions))
-    var userActions
-        get() = _userActions
-        set(value) {
-            _userActions = checkValid(value)
-        }
-
-    private fun checkValid(
-        userActions: Map<UserAction.Resolved, UserActionResult>
-    ): Map<UserAction.Resolved, UserActionResult> {
-        userActions.forEach { (action, result) ->
-            if (key == result.toScene) {
-                error(
-                    "Transition to the same content (scene/overlay) is not supported. Content " +
-                        "$key, action $action, result $result"
-                )
-            }
-        }
-        return userActions
-    }
+    var userActions by mutableStateOf(actions)
 
     @Composable
     fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index fdb019f..0cd8c1a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -26,7 +26,6 @@
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.OverlayKey
-import com.android.compose.animation.scene.OverscrollScope
 import com.android.compose.animation.scene.OverscrollSpecImpl
 import com.android.compose.animation.scene.ProgressVisibilityThreshold
 import com.android.compose.animation.scene.SceneKey
@@ -75,7 +74,7 @@
         val replacedTransition: Transition? = null,
     ) : TransitionState {
         /** A transition animating between [fromScene] and [toScene]. */
-        abstract class ChangeCurrentScene(
+        abstract class ChangeScene(
             /** The scene this transition is starting from. Can't be the same as toScene */
             val fromScene: SceneKey,
 
@@ -386,10 +385,10 @@
         val orientation: Orientation
 
         /**
-         * Scope which can be used in the Overscroll DSL to define a transformation based on the
-         * distance between [Transition.fromContent] and [Transition.toContent].
+         * Return the absolute distance between fromScene and toScene, if available, otherwise
+         * [DistanceUnspecified].
          */
-        val overscrollScope: OverscrollScope
+        val absoluteDistance: Float
 
         /**
          * The content (scene or overlay) around which the transition is currently bouncing. When
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 59bca50..8f84586 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene.transformation
 
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ContentKey
@@ -53,6 +54,8 @@
     val x: OverscrollScope.() -> Float = { 0f },
     val y: OverscrollScope.() -> Float = { 0f },
 ) : PropertyTransformation<Offset> {
+    private val cachedOverscrollScope = CachedOverscrollScope()
+
     override fun transform(
         layoutImpl: SceneTransitionLayoutImpl,
         content: ContentKey,
@@ -65,10 +68,47 @@
         // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
         // that this method was invoked after performing this check.
         val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+        val overscrollScope =
+            cachedOverscrollScope.getFromCacheOrCompute(layoutImpl.density, overscrollProperties)
 
         return Offset(
-            x = value.x + overscrollProperties.overscrollScope.x(),
-            y = value.y + overscrollProperties.overscrollScope.y(),
+            x = value.x + overscrollScope.x(),
+            y = value.y + overscrollScope.y(),
         )
     }
 }
+
+/**
+ * A helper class to cache a [OverscrollScope] given a [Density] and
+ * [TransitionState.HasOverscrollProperties]. This helps avoid recreating a scope every frame
+ * whenever an overscroll transition is computed.
+ */
+private class CachedOverscrollScope() {
+    private var previousScope: OverscrollScope? = null
+    private var previousDensity: Density? = null
+    private var previousOverscrollProperties: TransitionState.HasOverscrollProperties? = null
+
+    fun getFromCacheOrCompute(
+        density: Density,
+        overscrollProperties: TransitionState.HasOverscrollProperties,
+    ): OverscrollScope {
+        if (
+            previousScope == null ||
+                density != previousDensity ||
+                previousOverscrollProperties != overscrollProperties
+        ) {
+            val scope =
+                object : OverscrollScope, Density by density {
+                    override val absoluteDistance: Float
+                        get() = overscrollProperties.absoluteDistance
+                }
+
+            previousScope = scope
+            previousDensity = density
+            previousOverscrollProperties = overscrollProperties
+            return scope
+        }
+
+        return checkNotNull(previousScope)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 59ddb13..564d4b3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -27,7 +27,7 @@
     fromScene: SceneKey,
     toScene: SceneKey,
     override val key: TransitionKey? = null,
-) : TransitionState.Transition.ChangeCurrentScene(fromScene, toScene) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene) {
 
     override val currentScene: SceneKey
         get() {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 7d8e898..9fa4722 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -31,6 +31,8 @@
 import com.android.compose.animation.scene.NestedScrollBehavior.EdgeAlways
 import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoPreview
 import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview
+import com.android.compose.animation.scene.TestOverlays.OverlayA
+import com.android.compose.animation.scene.TestOverlays.OverlayB
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -52,7 +54,7 @@
 @RunWith(AndroidJUnit4::class)
 class DraggableHandlerTest {
     private class TestGestureScope(
-        private val testScope: MonotonicClockTestScope,
+        val testScope: MonotonicClockTestScope,
     ) {
         var canChangeScene: (SceneKey) -> Boolean = { true }
         val layoutState =
@@ -103,6 +105,21 @@
             ) {
                 Text("SceneC")
             }
+            overlay(
+                key = OverlayA,
+                userActions =
+                    mapOf(
+                        Swipe.Up to UserActionResult.HideOverlay(OverlayA),
+                        Swipe.Down to UserActionResult.ReplaceByOverlay(OverlayB)
+                    ),
+            ) {
+                Text("OverlayA")
+            }
+            overlay(
+                key = OverlayB,
+            ) {
+                Text("OverlayB")
+            }
         }
 
         val transitionInterceptionThreshold = 0.05f
@@ -117,7 +134,7 @@
                     builder = scenesBuilder,
                     coroutineScope = testScope,
                 )
-                .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
+                .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
 
         val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
         val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
@@ -459,13 +476,14 @@
     private fun TestGestureScope.navigateToSceneC() {
         assertIdle(currentScene = SceneA)
         val dragController = onDragStarted(overSlop = down(fractionOfScreen = 1f))
+        assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneC)
         dragController.onDragStopped(velocity = 0f)
         advanceUntilIdle()
         assertIdle(currentScene = SceneC)
     }
 
     @Test
-    fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
+    fun onAcceleratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
         val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
         assertTransition(
@@ -500,7 +518,7 @@
     }
 
     @Test
-    fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
+    fun onAcceleratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
         val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
         dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
         dragController1.onDragStopped(velocity = -velocityThreshold)
@@ -1276,4 +1294,87 @@
         assertThat(newTransition).isNotSameInstanceAs(transition)
         assertThat(newTransition.replacedTransition).isSameInstanceAs(transition)
     }
+
+    @Test
+    fun showOverlay() = runGestureTest {
+        mutableUserActionsA = mapOf(Swipe.Down to UserActionResult.ShowOverlay(OverlayA))
+
+        // Initial state.
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(/* empty */ )
+
+        // Swipe down to show overlay A.
+        val controller = onDragStarted(overSlop = down(0.1f))
+        val transition = assertThat(layoutState.transitionState).isShowOrHideOverlayTransition()
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasFromOrToScene(SceneA)
+        assertThat(transition).hasOverlay(OverlayA)
+        assertThat(transition).hasCurrentOverlays(/* empty, gesture not committed yet. */ )
+        assertThat(transition).hasProgress(0.1f)
+
+        // Commit the gesture. The overlay is instantly added in the set of current overlays.
+        controller.onDragStopped(velocityThreshold)
+        assertThat(transition).hasCurrentOverlays(OverlayA)
+        advanceUntilIdle()
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
+    }
+
+    @Test
+    fun hideOverlay() = runGestureTest {
+        layoutState.showOverlay(OverlayA, animationScope = testScope)
+        advanceUntilIdle()
+
+        // Initial state.
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
+
+        // Swipe up to hide overlay A.
+        val controller = onDragStarted(overSlop = up(0.1f))
+        val transition = assertThat(layoutState.transitionState).isShowOrHideOverlayTransition()
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasFromOrToScene(SceneA)
+        assertThat(transition).hasOverlay(OverlayA)
+        assertThat(transition).hasCurrentOverlays(OverlayA)
+        assertThat(transition).hasProgress(0.1f)
+
+        // Commit the gesture. The overlay is instantly removed from the set of current overlays.
+        controller.onDragStopped(-velocityThreshold)
+        assertThat(transition).hasCurrentOverlays(/* empty */ )
+        advanceUntilIdle()
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(/* empty */ )
+    }
+
+    @Test
+    fun replaceOverlay() = runGestureTest {
+        layoutState.showOverlay(OverlayA, animationScope = testScope)
+        advanceUntilIdle()
+
+        // Initial state.
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA)
+
+        // Swipe down to replace overlay A by overlay B.
+        val controller = onDragStarted(overSlop = down(0.1f))
+        val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition()
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasFromOverlay(OverlayA)
+        assertThat(transition).hasToOverlay(OverlayB)
+        assertThat(transition).hasCurrentOverlays(OverlayA)
+        assertThat(transition).hasProgress(0.1f)
+
+        // Commit the gesture. The overlays are instantly swapped in the set of current overlays.
+        controller.onDragStopped(velocityThreshold)
+        assertThat(transition).hasCurrentOverlays(OverlayB)
+        advanceUntilIdle()
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index f4e60a2..3f6bd2c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -69,7 +69,7 @@
                     interruptionHandler =
                         object : InterruptionHandler {
                             override fun onInterruption(
-                                interrupted: TransitionState.Transition.ChangeCurrentScene,
+                                interrupted: TransitionState.Transition.ChangeScene,
                                 newTargetScene: SceneKey
                             ): InterruptionResult {
                                 return InterruptionResult(
@@ -104,7 +104,7 @@
                     interruptionHandler =
                         object : InterruptionHandler {
                             override fun onInterruption(
-                                interrupted: TransitionState.Transition.ChangeCurrentScene,
+                                interrupted: TransitionState.Transition.ChangeScene,
                                 newTargetScene: SceneKey
                             ): InterruptionResult {
                                 return InterruptionResult(
@@ -198,7 +198,7 @@
     companion object {
         val FromToCurrentTriple =
             Correspondence.transforming(
-                { transition: TransitionState.Transition.ChangeCurrentScene? ->
+                { transition: TransitionState.Transition.ChangeScene? ->
                     Triple(transition?.fromScene, transition?.toScene, transition?.currentScene)
                 },
                 "(from, to, current) triple"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index a549d03..e4879d9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -163,7 +163,7 @@
                             fromContentZIndex: Float,
                             toContentZIndex: Float
                         ): ContentKey {
-                            transition as TransitionState.Transition.ChangeCurrentScene
+                            transition as TransitionState.Transition.ChangeScene
                             assertThat(transition).hasFromScene(SceneA)
                             assertThat(transition).hasToScene(SceneB)
                             assertThat(fromContentZIndex).isEqualTo(0)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 2d37a0d..d742592 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -68,7 +68,7 @@
             return delta
         }
 
-        override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
+        override fun onStop(velocity: Float, canChangeContent: Boolean): Float {
             onStop.invoke(velocity)
             return velocity
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 85db418..bec2bb2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -275,7 +275,7 @@
         rule.setContent {
             SceneTransitionLayout(state, Modifier.size(200.dp)) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
-                overlay(OverlayA, alignment) { Foo() }
+                overlay(OverlayA, alignment = alignment) { Foo() }
             }
         }
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 00c7588..c5b6cdf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -20,10 +20,16 @@
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestOverlays.OverlayA
+import com.android.compose.animation.scene.TestOverlays.OverlayB
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -198,6 +204,42 @@
         assertThat(canChangeSceneCalled).isFalse()
     }
 
+    @Test
+    fun backDismissesOverlayWithHighestZIndexByDefault() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    initialOverlays = setOf(OverlayA, OverlayB)
+                )
+            }
+
+        rule.setContent {
+            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) }
+                overlay(OverlayA) { Box(Modifier.fillMaxSize()) }
+                overlay(OverlayB) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        // Initial state.
+        rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+        rule.onNode(hasTestTag(OverlayA.testTag)).assertIsDisplayed()
+        rule.onNode(hasTestTag(OverlayB.testTag)).assertIsDisplayed()
+
+        // Press back. This should hide overlay B because it has a higher zIndex than overlay A.
+        rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+        rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+        rule.onNode(hasTestTag(OverlayA.testTag)).assertIsDisplayed()
+        rule.onNode(hasTestTag(OverlayB.testTag)).assertDoesNotExist()
+
+        // Press back again. This should hide overlay A.
+        rule.runOnUiThread { rule.activity.onBackPressedDispatcher.onBackPressed() }
+        rule.onNode(hasTestTag(SceneA.testTag)).assertIsDisplayed()
+        rule.onNode(hasTestTag(OverlayA.testTag)).assertDoesNotExist()
+        rule.onNode(hasTestTag(OverlayB.testTag)).assertDoesNotExist()
+    }
+
     private fun backEvent(progress: Float = 0f): BackEventCompat {
         return BackEventCompat(
             touchX = 0f,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 1f7fe37..467031a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -42,9 +42,9 @@
     orientation: Orientation = Orientation.Horizontal,
     onFinish: ((TransitionState.Transition) -> Job)? = null,
     replacedTransition: TransitionState.Transition? = null,
-): TransitionState.Transition.ChangeCurrentScene {
+): TransitionState.Transition.ChangeScene {
     return object :
-        TransitionState.Transition.ChangeCurrentScene(from, to, replacedTransition),
+        TransitionState.Transition.ChangeScene(from, to, replacedTransition),
         TransitionState.HasOverscrollProperties {
         override val currentScene: SceneKey
             get() = current()
@@ -69,12 +69,7 @@
         override val isUpOrLeft: Boolean = isUpOrLeft
         override val bouncingContent: ContentKey? = bouncingContent
         override val orientation: Orientation = orientation
-        override val overscrollScope: OverscrollScope =
-            object : OverscrollScope {
-                override val density: Float = 1f
-                override val fontScale: Float = 1f
-                override val absoluteDistance = 0f
-            }
+        override val absoluteDistance = 0f
 
         override fun finish(): Job {
             val onFinish =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index a98bd76..44e0ba5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene.subjects
 
 import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.OverscrollSpec
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.content.state.TransitionState
@@ -31,9 +32,25 @@
     return Truth.assertAbout(TransitionStateSubject.transitionStates()).that(state)
 }
 
-/** Assert on a [TransitionState.Transition.ChangeCurrentScene]. */
-fun assertThat(transition: TransitionState.Transition.ChangeCurrentScene): SceneTransitionSubject {
-    return Truth.assertAbout(SceneTransitionSubject.transitions()).that(transition)
+/** Assert on a [TransitionState.Transition.ChangeScene]. */
+fun assertThat(transition: TransitionState.Transition.ChangeScene): SceneTransitionSubject {
+    return Truth.assertAbout(SceneTransitionSubject.sceneTransitions()).that(transition)
+}
+
+/** Assert on a [TransitionState.Transition.ShowOrHideOverlay]. */
+fun assertThat(
+    transition: TransitionState.Transition.ShowOrHideOverlay,
+): ShowOrHideOverlayTransitionSubject {
+    return Truth.assertAbout(ShowOrHideOverlayTransitionSubject.showOrHideOverlayTransitions())
+        .that(transition)
+}
+
+/** Assert on a [TransitionState.Transition.ReplaceOverlay]. */
+fun assertThat(
+    transition: TransitionState.Transition.ReplaceOverlay,
+): ReplaceOverlayTransitionSubject {
+    return Truth.assertAbout(ReplaceOverlayTransitionSubject.replaceOverlayTransitions())
+        .that(transition)
 }
 
 class TransitionStateSubject
@@ -45,6 +62,10 @@
         check("currentScene").that(actual.currentScene).isEqualTo(sceneKey)
     }
 
+    fun hasCurrentOverlays(vararg overlays: OverlayKey) {
+        check("currentOverlays").that(actual.currentOverlays).containsExactlyElementsIn(overlays)
+    }
+
     fun isIdle(): TransitionState.Idle {
         if (actual !is TransitionState.Idle) {
             failWithActual(simpleFact("expected to be TransitionState.Idle"))
@@ -53,14 +74,32 @@
         return actual as TransitionState.Idle
     }
 
-    fun isSceneTransition(): TransitionState.Transition.ChangeCurrentScene {
-        if (actual !is TransitionState.Transition.ChangeCurrentScene) {
+    fun isSceneTransition(): TransitionState.Transition.ChangeScene {
+        if (actual !is TransitionState.Transition.ChangeScene) {
             failWithActual(
                 simpleFact("expected to be TransitionState.Transition.ChangeCurrentScene")
             )
         }
 
-        return actual as TransitionState.Transition.ChangeCurrentScene
+        return actual as TransitionState.Transition.ChangeScene
+    }
+
+    fun isShowOrHideOverlayTransition(): TransitionState.Transition.ShowOrHideOverlay {
+        if (actual !is TransitionState.Transition.ShowOrHideOverlay) {
+            failWithActual(
+                simpleFact("expected to be TransitionState.Transition.ShowOrHideOverlay")
+            )
+        }
+
+        return actual as TransitionState.Transition.ShowOrHideOverlay
+    }
+
+    fun isReplaceOverlayTransition(): TransitionState.Transition.ReplaceOverlay {
+        if (actual !is TransitionState.Transition.ReplaceOverlay) {
+            failWithActual(simpleFact("expected to be TransitionState.Transition.ReplaceOverlay"))
+        }
+
+        return actual as TransitionState.Transition.ReplaceOverlay
     }
 
     companion object {
@@ -70,21 +109,16 @@
     }
 }
 
-class SceneTransitionSubject
-private constructor(
+abstract class BaseTransitionSubject<T : TransitionState.Transition>(
     metadata: FailureMetadata,
-    private val actual: TransitionState.Transition.ChangeCurrentScene,
+    protected val actual: T,
 ) : Subject(metadata, actual) {
     fun hasCurrentScene(sceneKey: SceneKey) {
         check("currentScene").that(actual.currentScene).isEqualTo(sceneKey)
     }
 
-    fun hasFromScene(sceneKey: SceneKey) {
-        check("fromScene").that(actual.fromScene).isEqualTo(sceneKey)
-    }
-
-    fun hasToScene(sceneKey: SceneKey) {
-        check("toScene").that(actual.toScene).isEqualTo(sceneKey)
+    fun hasCurrentOverlays(vararg overlays: OverlayKey) {
+        check("currentOverlays").that(actual.currentOverlays).containsExactlyElementsIn(overlays)
     }
 
     fun hasProgress(progress: Float, tolerance: Float = 0f) {
@@ -144,11 +178,67 @@
             .that((actual as TransitionState.HasOverscrollProperties).bouncingContent)
             .isEqualTo(content)
     }
+}
+
+class SceneTransitionSubject
+private constructor(
+    metadata: FailureMetadata,
+    actual: TransitionState.Transition.ChangeScene,
+) : BaseTransitionSubject<TransitionState.Transition.ChangeScene>(metadata, actual) {
+    fun hasFromScene(sceneKey: SceneKey) {
+        check("fromScene").that(actual.fromScene).isEqualTo(sceneKey)
+    }
+
+    fun hasToScene(sceneKey: SceneKey) {
+        check("toScene").that(actual.toScene).isEqualTo(sceneKey)
+    }
 
     companion object {
-        fun transitions() =
-            Factory { metadata, actual: TransitionState.Transition.ChangeCurrentScene ->
+        fun sceneTransitions() =
+            Factory { metadata, actual: TransitionState.Transition.ChangeScene ->
                 SceneTransitionSubject(metadata, actual)
             }
     }
 }
+
+class ShowOrHideOverlayTransitionSubject
+private constructor(
+    metadata: FailureMetadata,
+    actual: TransitionState.Transition.ShowOrHideOverlay,
+) : BaseTransitionSubject<TransitionState.Transition.ShowOrHideOverlay>(metadata, actual) {
+    fun hasFromOrToScene(fromOrToScene: SceneKey) {
+        check("fromOrToScene").that(actual.fromOrToScene).isEqualTo(fromOrToScene)
+    }
+
+    fun hasOverlay(overlay: OverlayKey) {
+        check("overlay").that(actual.overlay).isEqualTo(overlay)
+    }
+
+    companion object {
+        fun showOrHideOverlayTransitions() =
+            Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay ->
+                ShowOrHideOverlayTransitionSubject(metadata, actual)
+            }
+    }
+}
+
+class ReplaceOverlayTransitionSubject
+private constructor(
+    metadata: FailureMetadata,
+    actual: TransitionState.Transition.ReplaceOverlay,
+) : BaseTransitionSubject<TransitionState.Transition.ReplaceOverlay>(metadata, actual) {
+    fun hasFromOverlay(fromOverlay: OverlayKey) {
+        check("fromOverlay").that(actual.fromOverlay).isEqualTo(fromOverlay)
+    }
+
+    fun hasToOverlay(toOverlay: OverlayKey) {
+        check("toOverlay").that(actual.toOverlay).isEqualTo(toOverlay)
+    }
+
+    companion object {
+        fun replaceOverlayTransitions() =
+            Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay ->
+                ReplaceOverlayTransitionSubject(metadata, actual)
+            }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 1ebd3d9..c5a5173 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -200,8 +200,8 @@
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(currentScene) { currentSceneContent() }
-                overlay(from, fromAlignment) { fromContent() }
-                overlay(to, toAlignment) { toContent() }
+                overlay(from, alignment = fromAlignment) { fromContent() }
+                overlay(to, alignment = toAlignment) { toContent() }
             }
         },
         changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
new file mode 100644
index 0000000..f6f98e9
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f7f69d3..ca585ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -56,6 +56,7 @@
 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.Kosmos
@@ -279,7 +280,7 @@
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
                 devicePolicyManager,
-                keyguardTransitionInteractor,
+                kosmos.keyguardDismissTransitionInteractor,
                 { primaryBouncerInteractor },
             ) {
                 deviceEntryInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/SystemActionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/extradim/ExtraDimDialogReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpanTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/PositionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesCheckerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index d244482..58c3fec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,15 +37,14 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.touch.scrim.ScrimController;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -57,7 +58,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Collections;
 import java.util.Optional;
 
 @SmallTest
@@ -106,15 +106,13 @@
     UiEventLogger mUiEventLogger;
 
     @Mock
-    LockPatternUtils mLockPatternUtils;
-
-    @Mock
     ActivityStarter mActivityStarter;
 
     @Mock
     CommunalViewModel mCommunalViewModel;
 
-    FakeUserTracker mUserTracker;
+    @Mock
+    KeyguardInteractor mKeyguardInteractor;
 
     private static final float TOUCH_REGION = .3f;
     private static final float MIN_BOUNCER_HEIGHT = .05f;
@@ -130,7 +128,6 @@
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
         MockitoAnnotations.initMocks(this);
-        mUserTracker = new FakeUserTracker();
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mKosmos.getTestScope(),
                 mScrimManager,
@@ -138,24 +135,21 @@
                 mNotificationShadeWindowController,
                 mValueAnimatorCreator,
                 mVelocityTrackerFactory,
-                mLockPatternUtils,
-                mUserTracker,
                 mCommunalViewModel,
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
                 MIN_BOUNCER_HEIGHT,
                 mUiEventLogger,
-                mActivityStarter);
+                mActivityStarter,
+                mKeyguardInteractor);
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
-        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
-
-        mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+        when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
     }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index b85e32b..9568167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -44,16 +46,15 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.touch.scrim.ScrimController;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -69,7 +70,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Collections;
 import java.util.Optional;
 
 @SmallTest
@@ -116,9 +116,6 @@
     UiEventLogger mUiEventLogger;
 
     @Mock
-    LockPatternUtils mLockPatternUtils;
-
-    @Mock
     ActivityStarter mActivityStarter;
 
     @Mock
@@ -127,11 +124,12 @@
     @Mock
     CommunalViewModel mCommunalViewModel;
 
+    @Mock
+    KeyguardInteractor mKeyguardInteractor;
+
     @Captor
     ArgumentCaptor<Rect> mRectCaptor;
 
-    FakeUserTracker mUserTracker;
-
     private static final float TOUCH_REGION = .3f;
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
@@ -148,7 +146,6 @@
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
         MockitoAnnotations.initMocks(this);
-        mUserTracker = new FakeUserTracker();
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mKosmos.getTestScope(),
                 mScrimManager,
@@ -156,24 +153,21 @@
                 mNotificationShadeWindowController,
                 mValueAnimatorCreator,
                 mVelocityTrackerFactory,
-                mLockPatternUtils,
-                mUserTracker,
                 mCommunalViewModel,
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
                 MIN_BOUNCER_HEIGHT,
                 mUiEventLogger,
-                mActivityStarter);
+                mActivityStarter,
+                mKeyguardInteractor);
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
-        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
-
-        mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+        when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
     }
 
     /**
@@ -391,7 +385,7 @@
      */
     @Test
     public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
-        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+        when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(true));
         mTouchHandler.onSessionStart(mTouchSession);
         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
@@ -426,7 +420,7 @@
      */
     @Test
     public void testSwipeDown_keyguardNotSecure_doesNotExpand() {
-        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+        when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(true));
         mTouchHandler.onSessionStart(mTouchSession);
         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/InputSessionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/BackTransformationTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/assist/ui/DisplayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/battery/BatterySpecsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
similarity index 80%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
index baef620..a36b0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
@@ -218,4 +218,60 @@
         assertThat(underTest.getMessageToShow(startWindow)?.msgId)
             .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
     }
+
+    @Test
+    fun messageMustMeetThreshold() {
+        underTest =
+            FaceHelpMessageDebouncer(
+                window = window,
+                startWindow = 0,
+                shownFaceMessageFrequencyBoost = 0,
+                threshold = .8f,
+            )
+
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+                "tooBright",
+                0
+            )
+        )
+
+        // although tooClose message is the majority, it doesn't meet the 80% threshold
+        assertThat(underTest.getMessageToShow(startWindow)).isNull()
+
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+        underTest.addMessage(
+            HelpFaceAuthenticationStatus(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+                "tooClose",
+                0
+            )
+        )
+
+        // message shows once it meets the threshold
+        assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+            .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+        assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index b31f6f5..add7a7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.biometrics
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
@@ -31,14 +35,29 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @android.platform.test.annotations.EnabledOnRavenwood
-class FaceHelpMessageDeferralTest : SysuiTestCase() {
+class FaceHelpMessageDeferralTest(flags: FlagsParameterization) : SysuiTestCase() {
     val threshold = .75f
     @Mock lateinit var logger: BiometricMessageDeferralLogger
     @Mock lateinit var dumpManager: DumpManager
+    val systemClock = FakeSystemClock()
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
@@ -111,10 +130,11 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
     fun testReturnsMostFrequentDeferredMessage() {
         val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
 
-        // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2
+        // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2
         biometricMessageDeferral.processFrame(1)
         biometricMessageDeferral.processFrame(1)
         biometricMessageDeferral.processFrame(1)
@@ -124,7 +144,41 @@
         biometricMessageDeferral.processFrame(2)
         biometricMessageDeferral.updateMessage(2, "msgId-2")
 
-        // THEN the most frequent deferred message is that meets the threshold is returned
+        // THEN the most frequent deferred message that meets the threshold is returned
+        assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+    fun testReturnsMostFrequentDeferredMessage_onlyAnalyzesLastNWindow() {
+        val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+        // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
+        // N window only contains messages with msgId=2
+        repeat(80) { biometricMessageDeferral.processFrame(1) }
+        biometricMessageDeferral.updateMessage(1, "msgId-1")
+        systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
+        repeat(20) { biometricMessageDeferral.processFrame(2) }
+        biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+        // THEN the most frequent deferred message in the last N window (500L) is returned
+        assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FACE_MESSAGE_DEFER_UPDATE)
+    fun testReturnsMostFrequentDeferredMessage_analyzesAllFrames() {
+        val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+        // WHEN there's 80% of the messages are msgId=1 and 20% is msgId=2, but the last
+        // N window only contains messages with msgId=2
+        repeat(80) { biometricMessageDeferral.processFrame(1) }
+        biometricMessageDeferral.updateMessage(1, "msgId-1")
+        systemClock.setElapsedRealtime(systemClock.elapsedRealtime() + 501L)
+        repeat(20) { biometricMessageDeferral.processFrame(2) }
+        biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+        // THEN the most frequent deferred message is returned
         assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
     }
 
@@ -213,14 +267,17 @@
     private fun createMsgDeferral(
         messagesToDefer: Set<Int>,
         acquiredInfoToIgnore: Set<Int> = emptySet(),
+        windowToAnalyzeLastNFrames: Long = 500L,
     ): BiometricMessageDeferral {
         return BiometricMessageDeferral(
-            messagesToDefer,
-            acquiredInfoToIgnore,
-            threshold,
-            logger,
-            dumpManager,
-            "0",
+            messagesToDefer = messagesToDefer,
+            acquiredInfoToIgnore = acquiredInfoToIgnore,
+            threshold = threshold,
+            windowToAnalyzeLastNFrames = windowToAnalyzeLastNFrames,
+            logBuffer = logger,
+            dumpManager = dumpManager,
+            id = "0",
+            systemClock = { systemClock },
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index d850f17..65236f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -30,8 +30,6 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -90,8 +88,6 @@
             .thenReturn(needsEmergencyAffordance)
         whenever(telecomManager.isInCall).thenReturn(false)
 
-        kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
-
         kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
 
         kosmos.telecomManager = telecomManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraIntentsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/HistoryTrackerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/ProximityClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/TypeClassifierTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
index 5556b04..4c908dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -140,6 +140,41 @@
         }
 
     @Test
+    fun installSessions_ignoreNullPackageNameSessions() =
+        testScope.runTest {
+            val nullPackageSession =
+                SessionInfo().apply {
+                    sessionId = 1
+                    appPackageName = null
+                    appIcon = icon1
+                }
+            val wellFormedSession =
+                SessionInfo().apply {
+                    sessionId = 2
+                    appPackageName = "pkg_name"
+                    appIcon = icon2
+                }
+
+            defaultSessions = listOf(nullPackageSession, wellFormedSession)
+
+            whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+            whenever(packageInstaller.getSessionInfo(1)).thenReturn(nullPackageSession)
+            whenever(packageInstaller.getSessionInfo(2)).thenReturn(wellFormedSession)
+
+            val packageInstallerMonitor =
+                PackageInstallerMonitor(
+                    handler,
+                    kosmos.applicationCoroutineScope,
+                    logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+                    packageInstaller,
+                )
+
+            val sessions by
+                testScope.collectLastValue(packageInstallerMonitor.installSessionsForPrimaryUser)
+            assertThat(sessions?.size).isEqualTo(1)
+        }
+
+    @Test
     fun installSessions_newSessionsAreAdded() =
         testScope.runTest {
             val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
@@ -177,7 +212,7 @@
                 }
 
             // Session 1 finished successfully
-            callback.onFinished(1, /* success = */ true)
+            callback.onFinished(1, /* success= */ true)
             runCurrent()
 
             // Verify flow updated with session 1 removed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index 76920e4..3b0057d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -81,6 +81,7 @@
     @Test
     fun startDreamWhenTransitioningToHub() =
         testScope.runTest {
+            keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
             whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
index d251585..1a426d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
@@ -67,6 +68,7 @@
                 smartspaceController,
                 fakeExecutor,
                 systemClock,
+                logcatLogBuffer("CommunalSmartspaceRepositoryImplTest"),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 864795b..99fcbb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -352,7 +352,7 @@
 
             smartspaceRepository.setTimers(targets)
 
-            val smartspaceContent by collectLastValue(underTest.getOngoingContent(true))
+            val smartspaceContent by collectLastValue(underTest.ongoingContent)
             assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
             for (index in 0 until totalTargets) {
                 assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -368,7 +368,7 @@
             // Media is playing.
             mediaRepository.mediaActive()
 
-            val umoContent by collectLastValue(underTest.getOngoingContent(true))
+            val umoContent by collectLastValue(underTest.ongoingContent)
 
             assertThat(umoContent?.size).isEqualTo(1)
             assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -376,20 +376,6 @@
         }
 
     @Test
-    fun umo_mediaPlaying_doNotShowUmo() =
-        testScope.run {
-            // Tutorial completed.
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            // Media is playing.
-            mediaRepository.mediaActive()
-
-            val umoContent by collectLastValue(underTest.getOngoingContent(false))
-
-            assertThat(umoContent?.size).isEqualTo(0)
-        }
-
-    @Test
     fun ongoing_shouldOrderAndSizeByTimestamp() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -412,7 +398,7 @@
             val timer3 = smartspaceTimer("timer3", timestamp = 4L)
             smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
 
-            val ongoingContent by collectLastValue(underTest.getOngoingContent(true))
+            val ongoingContent by collectLastValue(underTest.ongoingContent)
             assertThat(ongoingContent?.size).isEqualTo(4)
             assertThat(ongoingContent?.get(0)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
@@ -791,14 +777,6 @@
         }
 
     @Test
-    fun showWidgetEditor_withPreselectedKey_startsActivity() =
-        testScope.runTest {
-            val widgetKey = CommunalContentModel.KEY.widget(123)
-            underTest.showWidgetEditor(preselectedKey = widgetKey)
-            verify(editWidgetsActivityStarter).startActivity(widgetKey)
-        }
-
-    @Test
     fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
         testScope.runTest {
             underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
@@ -1082,6 +1060,16 @@
             assertThat(disclaimerDismissed).isFalse()
         }
 
+    @Test
+    fun settingSelectedKey_flowUpdated() {
+        testScope.runTest {
+            val key = "test"
+            val selectedKey by collectLastValue(underTest.selectedKey)
+            underTest.setSelectedKey(key)
+            assertThat(selectedKey).isEqualTo(key)
+        }
+    }
+
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index ed7e910..dfb75ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor.OnSceneAboutToChangeListener
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.EditModeState
@@ -36,6 +37,11 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -58,6 +64,36 @@
         }
 
     @Test
+    fun changeScene_callsSceneStateProcessor() =
+        testScope.runTest {
+            val callback: OnSceneAboutToChangeListener = mock()
+            underTest.registerSceneStateProcessor(callback)
+
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+            verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+
+            underTest.changeScene(CommunalScenes.Communal, "test")
+            assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
+            verify(callback).onSceneAboutToChange(CommunalScenes.Communal, null)
+        }
+
+    @Test
+    fun changeScene_doesNotCallSceneStateProcessorForDuplicateState() =
+        testScope.runTest {
+            val callback: OnSceneAboutToChangeListener = mock()
+            underTest.registerSceneStateProcessor(callback)
+
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+            underTest.changeScene(CommunalScenes.Blank, "test")
+            assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
+
+            verify(callback, never()).onSceneAboutToChange(any(), anyOrNull())
+        }
+
+    @Test
     fun snapToScene() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 7e28e19..0bfcd24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -36,11 +36,15 @@
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -50,14 +54,14 @@
     private lateinit var underTest: CommunalTutorialInteractor
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
-    private lateinit var communalInteractor: CommunalInteractor
+    private lateinit var communalSceneInteractor: CommunalSceneInteractor
     private lateinit var userRepository: FakeUserRepository
 
     @Before
     fun setUp() {
         keyguardRepository = kosmos.fakeKeyguardRepository
         communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
-        communalInteractor = kosmos.communalInteractor
+        communalSceneInteractor = kosmos.communalSceneInteractor
         userRepository = kosmos.fakeUserRepository
 
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -158,7 +162,7 @@
             kosmos.setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank, "test")
+            communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
         }
@@ -171,7 +175,7 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank, "test")
+            communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
@@ -184,13 +188,14 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank, "test")
+            communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
 
-    private suspend fun goToCommunal() {
+    private suspend fun TestScope.goToCommunal() {
         kosmos.setCommunalAvailable(true)
-        communalInteractor.changeScene(CommunalScenes.Communal, "test")
+        communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+        runCurrent()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 8218178..179ba22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -20,9 +20,7 @@
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Intent
-import android.content.pm.ActivityInfo
 import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
 import android.content.pm.UserInfo
 import android.provider.Settings
 import android.view.accessibility.AccessibilityEvent
@@ -72,7 +70,6 @@
 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
 import org.mockito.Mockito.never
@@ -141,6 +138,7 @@
                 context,
                 accessibilityManager,
                 packageManager,
+                WIDGET_PICKER_PACKAGE_NAME,
             )
     }
 
@@ -259,18 +257,8 @@
     @Test
     fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
         testScope.runTest {
-            whenever(packageManager.resolveActivity(any(), anyInt())).then {
-                ResolveInfo().apply {
-                    activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
-                }
-            }
-
             val success =
-                underTest.onOpenWidgetPicker(
-                    testableResources.resources,
-                    packageManager,
-                    activityResultLauncher
-                )
+                underTest.onOpenWidgetPicker(testableResources.resources, activityResultLauncher)
 
             verify(activityResultLauncher).launch(any())
             assertTrue(success)
@@ -278,38 +266,14 @@
     }
 
     @Test
-    fun onOpenWidgetPicker_launcherActivityNotResolved_doesNotLaunchWidgetPickerActivity() {
-        testScope.runTest {
-            whenever(packageManager.resolveActivity(any(), anyInt())).thenReturn(null)
-
-            val success =
-                underTest.onOpenWidgetPicker(
-                    testableResources.resources,
-                    packageManager,
-                    activityResultLauncher
-                )
-
-            verify(activityResultLauncher, never()).launch(any())
-            assertFalse(success)
-        }
-    }
-
-    @Test
     fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
         testScope.runTest {
-            whenever(packageManager.resolveActivity(any(), anyInt())).then {
-                ResolveInfo().apply {
-                    activityInfo = ActivityInfo().apply { packageName = WIDGET_PICKER_PACKAGE_NAME }
-                }
-            }
-
             whenever(activityResultLauncher.launch(any()))
                 .thenThrow(ActivityNotFoundException::class.java)
 
             val success =
                 underTest.onOpenWidgetPicker(
                     testableResources.resources,
-                    packageManager,
                     activityResultLauncher,
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index fb151a8..780d357 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -97,6 +97,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -159,24 +160,27 @@
 
         communalInteractor = spy(kosmos.communalInteractor)
 
-        underTest =
-            CommunalViewModel(
-                kosmos.testDispatcher,
-                testScope,
-                kosmos.testScope.backgroundScope,
-                context.resources,
-                kosmos.keyguardTransitionInteractor,
-                kosmos.keyguardInteractor,
-                mock<KeyguardIndicationController>(),
-                kosmos.communalSceneInteractor,
-                communalInteractor,
-                kosmos.communalSettingsInteractor,
-                kosmos.communalTutorialInteractor,
-                kosmos.shadeInteractor,
-                mediaHost,
-                logcatLogBuffer("CommunalViewModelTest"),
-                metricsLogger,
-            )
+        underTest = createViewModel()
+    }
+
+    private fun createViewModel(): CommunalViewModel {
+        return CommunalViewModel(
+            kosmos.testDispatcher,
+            testScope,
+            kosmos.testScope.backgroundScope,
+            context.resources,
+            kosmos.keyguardTransitionInteractor,
+            kosmos.keyguardInteractor,
+            mock<KeyguardIndicationController>(),
+            kosmos.communalSceneInteractor,
+            communalInteractor,
+            kosmos.communalSettingsInteractor,
+            kosmos.communalTutorialInteractor,
+            kosmos.shadeInteractor,
+            mediaHost,
+            logcatLogBuffer("CommunalViewModelTest"),
+            metricsLogger,
+        )
     }
 
     @Test
@@ -754,7 +758,7 @@
 
             // updateViewVisibility is called when the flow is collected.
             assertThat(communalContent).isNotNull()
-            verify(mediaHost).updateViewVisibility()
+            verify(mediaHost, atLeastOnce()).updateViewVisibility()
         }
 
     @Test
@@ -784,6 +788,21 @@
             assertThat(touchAvailable).isTrue()
         }
 
+    @Test
+    fun selectedKey_changeAffectsAllInstances() =
+        testScope.runTest {
+            val model1 = createViewModel()
+            val selectedKey1 by collectLastValue(model1.selectedKey)
+            val model2 = createViewModel()
+            val selectedKey2 by collectLastValue(model2.selectedKey)
+
+            val key = "test"
+            model1.setSelectedKey(key)
+
+            assertThat(selectedKey1).isEqualTo(key)
+            assertThat(selectedKey2).isEqualTo(key)
+        }
+
     private suspend fun setIsMainUser(isMainUser: Boolean) {
         val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
         with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
index 5b629b9..48b42d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarterTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.testKosmos
@@ -62,7 +61,7 @@
     fun activityLaunch_intentIsWellFormed() {
         with(kosmos) {
             testScope.runTest {
-                underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = true)
+                underTest.startActivity(shouldOpenWidgetPickerOnStart = true)
 
                 val captor = argumentCaptor<Intent>()
                 verify(activityStarter)
@@ -71,8 +70,6 @@
                 assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
                 assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
                     .isNotEqualTo(0)
-                assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
-                    .isEqualTo(TEST_PRESELECTED_KEY)
                 assertThat(
                         captor.lastValue.extras?.getBoolean(
                             EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -80,7 +77,7 @@
                     )
                     .isEqualTo(true)
 
-                underTest.startActivity(TEST_PRESELECTED_KEY, shouldOpenWidgetPickerOnStart = false)
+                underTest.startActivity(shouldOpenWidgetPickerOnStart = false)
 
                 verify(activityStarter, times(2))
                     .startActivityDismissingKeyguard(captor.capture(), eq(true), eq(true), any())
@@ -88,8 +85,6 @@
                 assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_NEW_TASK).isNotEqualTo(0)
                 assertThat(captor.lastValue.flags and Intent.FLAG_ACTIVITY_CLEAR_TASK)
                     .isNotEqualTo(0)
-                assertThat(captor.lastValue.extras?.getString(EXTRA_PRESELECTED_KEY))
-                    .isEqualTo(TEST_PRESELECTED_KEY)
                 assertThat(
                         captor.lastValue.extras?.getBoolean(
                             EditWidgetsActivity.EXTRA_OPEN_WIDGET_PICKER_ON_START
@@ -99,8 +94,4 @@
             }
         }
     }
-
-    companion object {
-        const val TEST_PRESELECTED_KEY = "test-key"
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationHostViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationTypesUpdaterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationUtilsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationUtilsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/complication/SmartSpaceComplicationTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/CustomIconCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AllModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/AppAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/StartActivityData.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TestableControlsActivity.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ToggleRangeTemplateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/coroutines/FlowTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 88ba041..296a0fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -88,21 +88,14 @@
     }
 
     @Test
-    fun testWakeUpSetsExitAnimationsRunning() {
-        controller.wakeUp()
-
-        verify(stateController).setExitAnimationsRunning(true)
-    }
-
-    @Test
-    fun testWakeUpAfterStartWillCancel() {
+    fun testOnWakeUpAfterStartWillCancel() {
         val mockStartAnimator: AnimatorSet = mock()
 
         controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
 
         verify(mockStartAnimator, never()).cancel()
 
-        controller.wakeUp()
+        controller.onWakeUp()
 
         // Verify that we cancelled the start animator in favor of the exit
         // animator.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 29aa89c..d21a827 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.dreams
 
+import android.app.WindowConfiguration
 import android.content.ComponentName
 import android.content.Intent
 import android.os.RemoteException
@@ -65,6 +66,8 @@
 import com.android.systemui.keyguard.gesture.domain.gestureInteractor
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.navigationbar.gestural.domain.TaskInfo
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
 import com.android.systemui.testKosmos
 import com.android.systemui.touch.TouchInsetManager
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -83,13 +86,17 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.firstValue
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -315,6 +322,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -332,6 +340,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -351,6 +360,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -373,6 +383,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -391,6 +402,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -406,6 +418,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -413,6 +426,47 @@
     }
 
     @Test
+    fun testDeferredResetRespondsToAnimationEnd() {
+        val client = client
+
+        // Inform the overlay service of dream starting.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            true /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+
+        whenever(mStateController.areExitAnimationsRunning()).thenReturn(true)
+        clearInvocations(mStateController, mTouchMonitor)
+
+        // Starting a dream will cause it to end first.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            true /*shouldShowComplication*/
+        )
+
+        mMainExecutor.runAllReady()
+
+        verifyZeroInteractions(mTouchMonitor)
+
+        val captor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback::class.java)
+        verify(mStateController).addCallback(captor.capture())
+
+        whenever(mStateController.areExitAnimationsRunning()).thenReturn(false)
+
+        captor.firstValue.onStateChanged()
+
+        // Should only be called once since it should be null during the second reset.
+        verify(mTouchMonitor).destroy()
+    }
+
+    @Test
     fun testLowLightSetByStartDream() {
         val client = client
 
@@ -421,6 +475,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -437,6 +492,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             HOME_CONTROL_PANEL_DREAM_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -453,6 +509,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -487,6 +544,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         // Immediately end the dream.
@@ -518,6 +576,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -537,6 +596,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -588,6 +648,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -611,6 +672,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -631,6 +693,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -660,18 +723,19 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
         mService.onWakeUp()
-        verify(mDreamOverlayContainerViewController).wakeUp()
+        verify(mDreamOverlayContainerViewController).onWakeUp()
         verify(mDreamOverlayCallbackController).onWakeUp()
     }
 
     @Test
     fun testWakeUpBeforeStartDoesNothing() {
         mService.onWakeUp()
-        verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp()
+        verify(mDreamOverlayContainerViewController, Mockito.never()).onWakeUp()
     }
 
     @Test
@@ -683,6 +747,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -708,6 +773,7 @@
                 mWindowParams,
                 mDreamOverlayCallback,
                 DREAM_COMPONENT,
+                false /*isPreview*/,
                 false /*shouldShowComplication*/
             )
             mMainExecutor.runAllReady()
@@ -733,6 +799,7 @@
                 mWindowParams,
                 mDreamOverlayCallback,
                 DREAM_COMPONENT,
+                false /*isPreview*/,
                 false /*shouldShowComplication*/
             )
             // Set communal available, verify that overlay callback is informed.
@@ -761,6 +828,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -781,6 +849,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -800,6 +869,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -823,6 +893,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -853,6 +924,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         testScope.runCurrent()
@@ -869,6 +941,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -892,6 +965,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -923,6 +997,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -954,6 +1029,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -989,6 +1065,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -1015,7 +1092,7 @@
     }
 
     @Test
-    fun testDreamActivityGesturesBlockedOnStart() {
+    fun testDreamActivityGesturesBlockedWhenDreaming() {
         val client = client
 
         // Inform the overlay service of dream starting.
@@ -1023,36 +1100,42 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
-        val captor = argumentCaptor<ComponentName>()
+
+        val matcherCaptor = argumentCaptor<TaskMatcher>()
         verify(gestureInteractor)
-            .addGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
-        assertThat(captor.firstValue.packageName)
-            .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
-    }
+            .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
+        val matcher = matcherCaptor.firstValue
 
-    @Test
-    fun testDreamActivityGesturesUnblockedOnEnd() {
-        val client = client
-
-        // Inform the overlay service of dream starting.
-        client.startDream(
-            mWindowParams,
-            mDreamOverlayCallback,
-            DREAM_COMPONENT,
-            false /*shouldShowComplication*/
-        )
-        mMainExecutor.runAllReady()
+        val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
+        assertThat(matcher.matches(dreamTaskInfo)).isTrue()
 
         client.endDream()
         mMainExecutor.runAllReady()
-        val captor = argumentCaptor<ComponentName>()
+
         verify(gestureInteractor)
-            .removeGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
-        assertThat(captor.firstValue.packageName)
-            .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
+            .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
+    }
+
+    @Test
+    fun testDreamActivityGesturesNotBlockedWhenPreview() {
+        val client = client
+
+        // Inform the overlay service of dream starting.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            true /*isPreview*/,
+            false /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+
+        verify(gestureInteractor, never())
+            .addGestureBlockedMatcher(any(), eq(GestureInteractor.Scope.Global))
     }
 
     @Test
@@ -1077,6 +1160,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 3aed79f..ca15eff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.education.domain.interactor
 
 import android.content.pm.UserInfo
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGestureEvent
+import android.view.KeyEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -41,6 +44,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -203,6 +209,31 @@
             assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
         }
 
+    @Test
+    fun updateShortcutTimeOnKeyboardShortcutTriggered() =
+        testScope.runTest {
+            // runCurrent() to trigger inputManager#registerKeyGestureEventListener in the
+            // interactor
+            runCurrent()
+            val listenerCaptor =
+                ArgumentCaptor.forClass(InputManager.KeyGestureEventListener::class.java)
+            verify(kosmos.mockEduInputManager)
+                .registerKeyGestureEventListener(any(), listenerCaptor.capture())
+
+            val backGestureEvent =
+                KeyGestureEvent(
+                    /* deviceId= */ 1,
+                    intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+                    KeyEvent.META_META_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+                )
+            listenerCaptor.value.onKeyGestureEvent(backGestureEvent)
+
+            val model by
+                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant())
+        }
+
     private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
         // Increment max number of signal to try triggering education
         for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
index 91d37cf..a764256 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.gesture.data
 
+import android.app.WindowConfiguration
 import android.content.ComponentName
 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.navigationbar.gestural.data.respository.GestureRepositoryImpl
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -40,14 +42,36 @@
     @Test
     fun addRemoveComponentToBlock_updatesBlockedComponentSet() =
         testScope.runTest {
-            val component = mock<ComponentName>()
+            val matcher = TaskMatcher.TopActivityComponent(mock<ComponentName>())
 
-            underTest.addGestureBlockedActivity(component)
-            val addedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
-            assertThat(addedBlockedComponents).contains(component)
+            kotlin.run {
+                underTest.addGestureBlockedMatcher(matcher)
+                val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+                assertThat(blockedMatchers).contains(matcher)
+            }
 
-            underTest.removeGestureBlockedActivity(component)
-            val removedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
-            assertThat(removedBlockedComponents).isEmpty()
+            kotlin.run {
+                underTest.removeGestureBlockedMatcher(matcher)
+                val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+                assertThat(blockedMatchers).doesNotContain(matcher)
+            }
+        }
+
+    @Test
+    fun addRemoveActivityTypeToBlock_updatesBlockedActivityTypesSet() =
+        testScope.runTest {
+            val matcher = TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+
+            kotlin.run {
+                underTest.addGestureBlockedMatcher(matcher)
+                val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+                assertThat(blockedMatchers).contains(matcher)
+            }
+
+            kotlin.run {
+                underTest.removeGestureBlockedMatcher(matcher)
+                val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers)
+                assertThat(blockedMatchers).doesNotContain(matcher)
+            }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
index 6395448..0ce0d93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.navigationbar.gestural.data.gestureRepository
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
 import com.android.systemui.shared.system.activityManagerWrapper
 import com.android.systemui.shared.system.taskStackChangeListeners
 import com.android.systemui.testKosmos
@@ -76,13 +77,16 @@
     fun addBlockedActivity_testCombination() =
         testScope.runTest {
             val globalComponent = mock<ComponentName>()
-            repository.addGestureBlockedActivity(globalComponent)
+            repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent))
 
             val localComponent = mock<ComponentName>()
 
             val blocked by collectLastValue(underTest.topActivityBlocked)
 
-            underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+            underTest.addGestureBlockedMatcher(
+                TaskMatcher.TopActivityComponent(localComponent),
+                GestureInteractor.Scope.Local
+            )
 
             assertThat(blocked).isFalse()
 
@@ -95,7 +99,7 @@
     fun initialization_testEmit() =
         testScope.runTest {
             val globalComponent = mock<ComponentName>()
-            repository.addGestureBlockedActivity(globalComponent)
+            repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent))
             setTopActivity(globalComponent)
 
             val interactor = createInteractor()
@@ -114,10 +118,36 @@
 
             val localComponent = mock<ComponentName>()
 
-            interactor1.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+            interactor1.addGestureBlockedMatcher(
+                TaskMatcher.TopActivityComponent(localComponent),
+                GestureInteractor.Scope.Local
+            )
             setTopActivity(localComponent)
 
             assertThat(interactor1Blocked).isTrue()
             assertThat(interactor2Blocked).isFalse()
         }
+
+    @Test
+    fun matchingBlockers_separatelyManaged() =
+        testScope.runTest {
+            val interactor = createInteractor()
+            val interactorBlocked by collectLastValue(interactor.topActivityBlocked)
+
+            val localComponent = mock<ComponentName>()
+
+            val matcher1 = TaskMatcher.TopActivityComponent(localComponent)
+            val matcher2 = TaskMatcher.TopActivityComponent(localComponent)
+
+            interactor.addGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local)
+            interactor.addGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local)
+            setTopActivity(localComponent)
+            assertThat(interactorBlocked).isTrue()
+
+            interactor.removeGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local)
+            assertThat(interactorBlocked).isTrue()
+
+            interactor.removeGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local)
+            assertThat(interactorBlocked).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt
new file mode 100644
index 0000000..a246270
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.gesture.domain
+
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.gestural.domain.TaskInfo
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TaskMatcherTest : SysuiTestCase() {
+    @Test
+    fun activityMatcher_matchesComponentName() {
+        val componentName = ComponentName.unflattenFromString("com.foo/.bar")!!
+        val matcher = TaskMatcher.TopActivityComponent(componentName)
+
+        val taskInfo = TaskInfo(componentName, WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+        assertThat(matcher.matches(taskInfo)).isTrue()
+    }
+
+    @Test
+    fun activityMatcher_doesNotMatchComponentName() {
+        val componentName = ComponentName.unflattenFromString("com.foo/.bar")!!
+        val matcher = TaskMatcher.TopActivityComponent(componentName)
+
+        val taskInfo =
+            TaskInfo(
+                ComponentName.unflattenFromString("com.bar/.baz"),
+                WindowConfiguration.ACTIVITY_TYPE_STANDARD
+            )
+        assertThat(matcher.matches(taskInfo)).isFalse()
+    }
+
+    @Test
+    fun activityMatcher_matchesActivityType() {
+        val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+        val matcher = TaskMatcher.TopActivityType(activityType)
+
+        val taskInfo = TaskInfo(mock<ComponentName>(), activityType)
+        assertThat(matcher.matches(taskInfo)).isTrue()
+    }
+
+    @Test
+    fun activityMatcher_doesNotMatchEmptyActivityType() {
+        val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+        val matcher = TaskMatcher.TopActivityType(activityType)
+
+        val taskInfo = TaskInfo(null, activityType)
+        assertThat(matcher.matches(taskInfo)).isFalse()
+    }
+
+    @Test
+    fun activityMatcher_doesNotMatchActivityType() {
+        val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+        val matcher = TaskMatcher.TopActivityType(activityType)
+
+        val taskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_STANDARD)
+        assertThat(matcher.matches(taskInfo)).isFalse()
+    }
+
+    @Test
+    fun activityMatcher_equivalentMatchersAreNotEqual() {
+        val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+        val matcher1 = TaskMatcher.TopActivityType(activityType)
+        val matcher2 = TaskMatcher.TopActivityType(activityType)
+
+        assertThat(matcher1).isNotEqualTo(matcher2)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 3b8ffcd..17e3006 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -116,7 +116,7 @@
             reset(transitionRepository)
 
             // Authentication results in calling startDismissKeyguardTransition.
-            kosmos.keyguardTransitionInteractor.startDismissKeyguardTransition()
+            kosmos.keyguardDismissTransitionInteractor.startDismissKeyguardTransition()
             runCurrent()
 
             assertThat(transitionRepository)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 6eb9862..33f3cd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -39,12 +39,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
@@ -53,6 +56,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.testKosmos
 import junit.framework.Assert.assertEquals
@@ -166,6 +170,10 @@
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
         testScope.runTest {
+            val isGone by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.AOD,
@@ -175,7 +183,7 @@
             runCurrent()
 
             // Make sure we're GONE.
-            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+            assertEquals(true, isGone)
 
             // Get part way to AOD.
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -204,6 +212,10 @@
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
         testScope.runTest {
+            val isGone by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.AOD,
@@ -213,7 +225,7 @@
             runCurrent()
 
             // Make sure we're GONE.
-            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+            assertEquals(true, isGone)
 
             // Get all the way to AOD
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -239,6 +251,10 @@
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
         testScope.runTest {
+            val isLockscreen by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Lockscreen, LOCKSCREEN)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.AOD,
@@ -248,10 +264,7 @@
             runCurrent()
 
             // Make sure we're in LOCKSCREEN.
-            assertEquals(
-                KeyguardState.LOCKSCREEN,
-                kosmos.keyguardTransitionInteractor.getFinishedState()
-            )
+            assertEquals(true, isLockscreen)
 
             // Get part way to AOD.
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -314,7 +327,7 @@
         testScope.runTest {
             kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
             kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
-            kosmos.keyguardTransitionInteractor.startDismissKeyguardTransition()
+            kosmos.keyguardDismissTransitionInteractor.startDismissKeyguardTransition()
             powerInteractor.setAwakeForTest()
             advanceTimeBy(100) // account for debouncing
 
@@ -327,6 +340,10 @@
     @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetectedInAod_fromGone() =
         testScope.runTest {
+            val isGone by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.AOD,
@@ -336,7 +353,7 @@
             runCurrent()
 
             // Make sure we're GONE.
-            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+            assertEquals(true, isGone)
 
             // Start going to AOD on first button push
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index ee4a0d2d..c18deb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -48,12 +48,15 @@
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -62,6 +65,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -316,6 +320,10 @@
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
         testScope.runTest {
+            val isGone by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.DOZING,
@@ -325,7 +333,7 @@
             runCurrent()
 
             // Make sure we're GONE.
-            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+            assertEquals(true, isGone)
 
             // Get part way to AOD.
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -355,6 +363,10 @@
     @Suppress("ktlint:standard:max-line-length")
     fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
         testScope.runTest {
+            val isGone by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.DOZING,
@@ -364,7 +376,7 @@
             runCurrent()
 
             // Make sure we're GONE.
-            assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+            assertEquals(true, isGone)
 
             // Get all the way to AOD
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
@@ -390,6 +402,10 @@
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
         testScope.runTest {
+            val isLockscreen by
+                collectLastValue(
+                    kosmos.keyguardTransitionInteractor.isFinishedIn(Scenes.Lockscreen, LOCKSCREEN)
+                )
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.DOZING,
@@ -399,10 +415,7 @@
             runCurrent()
 
             // Make sure we're in LOCKSCREEN.
-            assertEquals(
-                KeyguardState.LOCKSCREEN,
-                kosmos.keyguardTransitionInteractor.getFinishedState()
-            )
+            assertEquals(true, isLockscreen)
 
             // Get part way to AOD.
             powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6e76cbc..6708ffa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -90,52 +90,6 @@
         }
 
     @Test
-    fun finishedKeyguardStateTests() =
-        testScope.runTest {
-            val finishedSteps by collectValues(underTest.finishedKeyguardState)
-            runCurrent()
-            val steps = mutableListOf<TransitionStep>()
-
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-            steps.forEach {
-                repository.sendTransitionStep(it)
-                runCurrent()
-            }
-
-            assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
-        }
-
-    @Test
-    fun startedKeyguardStateTests() =
-        testScope.runTest {
-            val startedStates by collectValues(underTest.startedKeyguardState)
-            runCurrent()
-            val steps = mutableListOf<TransitionStep>()
-
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-            steps.forEach {
-                repository.sendTransitionStep(it)
-                runCurrent()
-            }
-
-            assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
-        }
-
-    @Test
     fun startedKeyguardTransitionStepTests() =
         testScope.runTest {
             val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
@@ -1206,95 +1160,6 @@
         }
 
     @Test
-    fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
-        testScope.runTest {
-            val finishedStates by collectValues(underTest.finishedKeyguardState)
-
-            // We default FINISHED in LOCKSCREEN.
-            assertEquals(listOf(LOCKSCREEN), finishedStates)
-
-            sendSteps(
-                TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
-                TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
-                TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
-            )
-
-            // We're FINISHED in AOD.
-            assertEquals(
-                listOf(
-                    LOCKSCREEN,
-                    AOD,
-                ),
-                finishedStates
-            )
-
-            // Transition back to LOCKSCREEN.
-            sendSteps(
-                TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
-                TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
-                TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
-            )
-
-            // We're FINISHED in LOCKSCREEN.
-            assertEquals(
-                listOf(
-                    LOCKSCREEN,
-                    AOD,
-                    LOCKSCREEN,
-                ),
-                finishedStates
-            )
-
-            sendSteps(
-                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
-                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
-            )
-
-            // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
-            // LOCKSCREEN.
-            assertEquals(
-                listOf(
-                    LOCKSCREEN,
-                    AOD,
-                    LOCKSCREEN,
-                ),
-                finishedStates
-            )
-
-            sendSteps(
-                TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
-            )
-
-            // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
-            assertEquals(
-                listOf(
-                    LOCKSCREEN,
-                    AOD,
-                    LOCKSCREEN,
-                ),
-                finishedStates
-            )
-
-            sendSteps(
-                TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
-                TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
-                TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
-            )
-
-            // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
-            // LOCKSCREEN after the cancellation.
-            assertEquals(
-                listOf(
-                    LOCKSCREEN,
-                    AOD,
-                    LOCKSCREEN,
-                    LOCKSCREEN,
-                ),
-                finishedStates
-            )
-        }
-
-    @Test
     fun testCurrentState() =
         testScope.runTest {
             val currentStates by collectValues(underTest.currentKeyguardState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 3e0a1f3..073ed61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -19,18 +19,20 @@
 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.keyguard.data.fakeLightRevealScrimRepository
+import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_EFFECT
 import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Test
@@ -64,35 +66,45 @@
 
     @Test
     fun lightRevealEffect_doesNotChangeDuringKeyguardTransition() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<LightRevealEffect>()
-            val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
+        kosmos.testScope.runTest {
+            val values by collectValues(underTest.lightRevealEffect)
+            runCurrent()
+            assertEquals(listOf(DEFAULT_REVEAL_EFFECT), values)
 
             fakeLightRevealScrimRepository.setRevealEffect(reveal1)
-
+            runCurrent()
             // The reveal effect shouldn't emit anything until a keyguard transition starts.
-            assertEquals(values.size, 0)
+            assertEquals(listOf(DEFAULT_REVEAL_EFFECT), values)
 
             // Once it starts, it should emit reveal1.
             fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(transitionState = TransitionState.STARTED)
+                TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED)
             )
-            assertEquals(values, listOf(reveal1))
+            runCurrent()
+            assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1), values)
 
             // Until the next transition starts, reveal2 should not be emitted.
             fakeLightRevealScrimRepository.setRevealEffect(reveal2)
+            runCurrent()
             fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(transitionState = TransitionState.RUNNING)
+                TransitionStep(
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.RUNNING
+                )
             )
+            runCurrent()
             fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(transitionState = TransitionState.FINISHED)
+                TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.FINISHED)
             )
-            assertEquals(values, listOf(reveal1))
+            runCurrent()
+            assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1), values)
             fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(transitionState = TransitionState.STARTED)
+                TransitionStep(
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED
+                )
             )
-            assertEquals(values, listOf(reveal1, reveal2))
-
-            job.cancel()
+            runCurrent()
+            assertEquals(listOf(DEFAULT_REVEAL_EFFECT, reveal1, reveal2), values)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
index b3ea03e..c66ebf3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -200,7 +201,7 @@
                         fromSource = Edge.Top.takeIf { downFromEdge },
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
-                )
+                ) as? UserActionResult.ChangeScene
             val downScene by
                 collectLastValue(
                     downDestination?.let {
@@ -226,9 +227,11 @@
 
             val upScene by
                 collectLastValue(
-                    destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene?.let { scene ->
-                        kosmos.sceneInteractor.resolveSceneFamily(scene)
-                    } ?: flowOf(null)
+                    (destinationScenes?.get(Swipe(SwipeDirection.Up))
+                            as? UserActionResult.ChangeScene)
+                        ?.toScene
+                        ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
+                        ?: flowOf(null)
                 )
 
             assertThat(upScene)
@@ -241,9 +244,11 @@
 
             val leftScene by
                 collectLastValue(
-                    destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene?.let { scene ->
-                        kosmos.sceneInteractor.resolveSceneFamily(scene)
-                    } ?: flowOf(null)
+                    (destinationScenes?.get(Swipe(SwipeDirection.Left))
+                            as? UserActionResult.ChangeScene)
+                        ?.toScene
+                        ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
+                        ?: flowOf(null)
                 )
 
             assertThat(leftScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
deleted file mode 100644
index f6f58c9..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
+++ /dev/null
@@ -1,328 +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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.lifecycle
-
-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.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BaseActivatableTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private val underTest = FakeActivatable()
-
-    @Test
-    fun activate() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-        }
-
-    @Test
-    fun activate_andCancel() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            val job = Job()
-            underTest.activateIn(testScope, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-        }
-
-    @Test
-    fun activate_afterCancellation() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            val job = Job()
-            underTest.activateIn(testScope, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(2)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-        }
-
-    @Test(expected = IllegalStateException::class)
-    fun activate_whileActive_throws() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-        }
-
-    @Test
-    fun addChild_beforeActive_activatesChildrenOnceActivated() =
-        testScope.runTest {
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            assertThat(underTest.isActive).isFalse()
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.activateIn(this)
-            runCurrent()
-
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-        }
-
-    @Test
-    fun addChild_whileActive_activatesChildrenImmediately() =
-        testScope.runTest {
-            underTest.activateIn(this)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            runCurrent()
-
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-        }
-
-    @Test
-    fun addChild_afterCancellation_doesNotActivateChildren() =
-        testScope.runTest {
-            val job = Job()
-            underTest.activateIn(this, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            runCurrent()
-
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-        }
-
-    @Test
-    fun activate_cancellation_cancelsCurrentChildren() =
-        testScope.runTest {
-            val job = Job()
-            underTest.activateIn(this, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            runCurrent()
-
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-        }
-
-    @Test
-    fun activate_afterCancellation_reactivatesCurrentChildren() =
-        testScope.runTest {
-            val job = Job()
-            underTest.activateIn(this, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            runCurrent()
-
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.activateIn(this)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-        }
-
-    @Test
-    fun removeChild_beforeActive_neverActivatesChild() =
-        testScope.runTest {
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            assertThat(underTest.isActive).isFalse()
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-        }
-
-    @Test
-    fun removeChild_whileActive_cancelsChild() =
-        testScope.runTest {
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            assertThat(underTest.isActive).isFalse()
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.activateIn(this)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-
-            underTest.removeChild(child1)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isTrue()
-        }
-
-    @Test
-    fun removeChild_afterCancellation_doesNotReactivateChildren() =
-        testScope.runTest {
-            val child1 = FakeActivatable()
-            val child2 = FakeActivatable()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            assertThat(underTest.isActive).isFalse()
-            underTest.addChild(child1)
-            underTest.addChild(child2)
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            val job = Job()
-            underTest.activateIn(this, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isTrue()
-            assertThat(child2.isActive).isTrue()
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isFalse()
-
-            underTest.removeChild(child1)
-            underTest.activateIn(this)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(child1.isActive).isFalse()
-            assertThat(child2.isActive).isTrue()
-        }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
new file mode 100644
index 0000000..81b9180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ExclusiveActivatableTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.lifecycle
+
+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.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ExclusiveActivatableTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = FakeActivatable()
+
+    @Test
+    fun activate() =
+        testScope.runTest {
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+        }
+
+    @Test
+    fun activate_andCancel() =
+        testScope.runTest {
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            val job = Job()
+            underTest.activateIn(testScope, context = job)
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+        }
+
+    @Test
+    fun activate_afterCancellation() =
+        testScope.runTest {
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            val job = Job()
+            underTest.activateIn(testScope, context = job)
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(2)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun activate_whileActive_throws() =
+        testScope.runTest {
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
new file mode 100644
index 0000000..ec6045c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.lifecycle
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HydratorTest : SysuiTestCase() {
+
+    @get:Rule val composeRule = createComposeRule()
+
+    @Test
+    fun hydratedStateOf() {
+        val keepAliveMutable = mutableStateOf(true)
+        val upstreamStateFlow = MutableStateFlow(true)
+        val upstreamFlow = upstreamStateFlow.map { !it }
+        composeRule.setContent {
+            val keepAlive by keepAliveMutable
+            if (keepAlive) {
+                val viewModel =
+                    rememberViewModel("test") {
+                        FakeSysUiViewModel(
+                            upstreamFlow = upstreamFlow,
+                            upstreamStateFlow = upstreamStateFlow,
+                        )
+                    }
+
+                Column {
+                    Text(
+                        "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
+                        Modifier.testTag("upstreamStateFlow")
+                    )
+                    Text(
+                        "upstreamFlow=${viewModel.stateBackedByFlow}",
+                        Modifier.testTag("upstreamFlow")
+                    )
+                }
+            }
+        }
+
+        composeRule.waitForIdle()
+        composeRule
+            .onNode(hasTestTag("upstreamStateFlow"))
+            .assertTextEquals("upstreamStateFlow=true")
+        composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
+
+        composeRule.runOnUiThread { upstreamStateFlow.value = false }
+        composeRule.waitForIdle()
+        composeRule
+            .onNode(hasTestTag("upstreamStateFlow"))
+            .assertTextEquals("upstreamStateFlow=false")
+        composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
index 8f925d5..0505e19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -68,7 +69,8 @@
             lockDevice()
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
@@ -82,7 +84,8 @@
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
         }
 
@@ -94,7 +97,8 @@
             unlockDevice()
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
@@ -107,7 +111,8 @@
             lockDevice()
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
@@ -122,7 +127,8 @@
             unlockDevice()
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
@@ -138,7 +144,8 @@
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
                 .isEqualTo(Scenes.Lockscreen)
         }
@@ -156,7 +163,8 @@
             sceneInteractor.changeScene(Scenes.Gone, "reason")
             underTest.activateIn(this)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 768fbca..7203b61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.composefragment.viewmodel
 
+import android.app.StatusBarManager
 import android.content.testableContext
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Lifecycle
@@ -32,6 +33,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -178,6 +181,23 @@
             }
         }
 
+    @Test
+    fun qsEnabled_followsRepository() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val qsEnabled by collectLastValue(underTest.qsEnabled)
+
+                fakeDisableFlagsRepository.disableFlags.value =
+                    DisableFlagsModel(disable2 = QS_DISABLE_FLAG)
+
+                assertThat(qsEnabled).isFalse()
+
+                fakeDisableFlagsRepository.disableFlags.value = DisableFlagsModel()
+
+                assertThat(qsEnabled).isTrue()
+            }
+        }
+
     private inline fun TestScope.testWithinLifecycle(
         crossinline block: suspend TestScope.() -> TestResult
     ): TestResult {
@@ -186,4 +206,8 @@
             block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
         }
     }
+
+    companion object {
+        private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
+    }
 }
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 e0a53f8..5925819 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
@@ -24,6 +24,7 @@
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.ZenIconLoader
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.coroutines.collectValues
@@ -35,6 +36,7 @@
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.toCollection
@@ -61,6 +63,7 @@
             addOverride(com.android.internal.R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
             addOverride(com.android.internal.R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE)
         }
+        ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService()))
     }
 
     @EnableFlags(Flags.FLAG_MODES_UI)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 1118a61..e2149d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.media.controls.shared.model.MediaData
@@ -82,7 +81,6 @@
                 footerActionsController = footerActionsController,
                 mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
             )
-        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
index 647fdf6..db58c85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -45,7 +46,6 @@
 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
 
@@ -63,19 +63,16 @@
 
     private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
 
-    @Before
-    fun setUp() {
-        underTest.activateIn(testScope)
-    }
-
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -83,24 +80,28 @@
     @Test
     fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             unlockDevice()
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Down)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -109,11 +110,13 @@
     fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
 
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -122,12 +125,14 @@
     fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
         testScope.runTest {
             kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             lockDevice()
             unlockDevice()
 
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(actions?.get(Swipe.Up)).isNull()
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -135,6 +140,7 @@
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -143,13 +149,15 @@
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
 
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -159,21 +167,25 @@
             runCurrent()
             sceneInteractor.changeScene(Scenes.Gone, "reason")
 
-            assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
 
     @Test
     fun backTransitionSceneKey_notEditing_Home() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
 
-            assertThat(actions?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
+            assertThat((actions?.get(Back) as? UserActionResult.ChangeScene)?.toScene)
+                .isEqualTo(SceneFamilies.Home)
         }
 
     @Test
     fun backTransition_editing_noDestination() =
         testScope.runTest {
+            underTest.activateIn(this)
             val actions by collectLastValue(underTest.actions)
             kosmos.editModeViewModel.startEditing()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c633816..f365afb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,6 +26,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
 import com.android.internal.R
 import com.android.internal.util.EmergencyAffordanceManager
 import com.android.internal.util.emergencyAffordanceManager
@@ -175,10 +176,7 @@
         emergencyAffordanceManager = kosmos.emergencyAffordanceManager
         whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
 
-        kosmos.fakeFeatureFlagsClassic.apply {
-            set(Flags.NEW_NETWORK_SLICE_UI, false)
-            set(Flags.REFACTOR_GETCURRENTUSER, true)
-        }
+        kosmos.fakeFeatureFlagsClassic.apply { set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
         mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
         mobileConnectionsRepository.isAnySimSecure.value = false
@@ -232,7 +230,8 @@
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -252,7 +251,8 @@
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -271,7 +271,8 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
             emulateUserDrivenTransition(
@@ -299,7 +300,8 @@
             emulateUserDrivenTransition(to = Scenes.Shade)
             assertCurrentScene(Scenes.Shade)
 
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
@@ -368,7 +370,8 @@
         testScope.runTest {
             unlockDevice()
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
         }
 
@@ -390,7 +393,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -408,7 +412,8 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -428,7 +433,8 @@
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
             val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -495,7 +501,7 @@
     private fun getCurrentSceneInUi(): SceneKey {
         return when (val state = transitionState.value) {
             is ObservableTransitionState.Idle -> state.currentScene
-            is ObservableTransitionState.Transition.ChangeCurrentScene -> state.fromScene
+            is ObservableTransitionState.Transition.ChangeScene -> state.fromScene
             is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
             is ObservableTransitionState.Transition.ReplaceOverlay -> state.currentScene
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index df30c4b..227b3a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.overlayKeys
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.model.Scenes
@@ -46,19 +47,9 @@
     private val testScope = kosmos.testScope
 
     @Test
-    fun allSceneKeys() {
+    fun allContentKeys() {
         val underTest = kosmos.sceneContainerRepository
-        assertThat(underTest.allSceneKeys())
-            .isEqualTo(
-                listOf(
-                    Scenes.QuickSettings,
-                    Scenes.Shade,
-                    Scenes.Lockscreen,
-                    Scenes.Bouncer,
-                    Scenes.Gone,
-                    Scenes.Communal,
-                )
-            )
+        assertThat(underTest.allContentKeys).isEqualTo(kosmos.sceneKeys + kosmos.overlayKeys)
     }
 
     @Test
@@ -75,6 +66,18 @@
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
         }
 
+    // TODO(b/356596436): Add tests for showing, hiding, and replacing overlays after we've defined
+    //  them.
+    @Test
+    fun currentOverlays() =
+        testScope.runTest {
+            val underTest = kosmos.sceneContainerRepository
+            val currentOverlays by collectLastValue(underTest.currentOverlays)
+            assertThat(currentOverlays).isEmpty()
+
+            // TODO(b/356596436): When we have a first overlay, add it here and assert contains.
+        }
+
     @Test(expected = IllegalStateException::class)
     fun changeScene_noSuchSceneInContainer_throws() {
         kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index e3a69a9..4a7d8b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
+import com.android.systemui.scene.overlayKeys
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -72,9 +73,11 @@
         kosmos.keyguardEnabledInteractor
     }
 
+    // TODO(b/356596436): Add tests for showing, hiding, and replacing overlays after we've defined
+    //  them.
     @Test
-    fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
+    fun allContentKeys() {
+        assertThat(underTest.allContentKeys).isEqualTo(kosmos.sceneKeys + kosmos.overlayKeys)
     }
 
     @Test
@@ -401,10 +404,10 @@
             underTest.setVisible(false, "reason")
             val isVisible by collectLastValue(underTest.isVisible)
             assertThat(isVisible).isFalse()
-            underTest.onRemoteUserInteractionStarted("reason")
+            underTest.onRemoteUserInputStarted("reason")
             assertThat(isVisible).isTrue()
 
-            underTest.onUserInteractionFinished()
+            underTest.onUserInputFinished()
 
             assertThat(isVisible).isFalse()
         }
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 8f8d2e2..d3b51d1 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
@@ -51,7 +51,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -551,8 +551,7 @@
     fun switchToAOD_whenAvailable_whenDeviceSleepsLocked() =
         testScope.runTest {
             kosmos.lockscreenSceneTransitionInteractor.start()
-            val asleepState by
-                collectLastValue(kosmos.keyguardTransitionInteractor.asleepKeyguardState)
+            val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
             val currentTransitionInfo by
                 collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
             val transitionState =
@@ -584,8 +583,7 @@
     fun switchToDozing_whenAodUnavailable_whenDeviceSleepsLocked() =
         testScope.runTest {
             kosmos.lockscreenSceneTransitionInteractor.start()
-            val asleepState by
-                collectLastValue(kosmos.keyguardTransitionInteractor.asleepKeyguardState)
+            val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState)
             val currentTransitionInfo by
                 collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal)
             val transitionState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
index 32c0172..2720c57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
@@ -37,6 +37,8 @@
     private val initialSceneKey = kosmos.initialSceneKey
     private val fakeSceneDataSource = kosmos.fakeSceneDataSource
 
+    // TODO(b/356596436): Add tests for showing, hiding, and replacing overlays after we've defined
+    //  them.
     private val underTest = kosmos.sceneDataSourceDelegator
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
index 206d3ac..900f2a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -35,7 +35,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -55,7 +54,6 @@
         testScope.runTest {
             val actions by collectLastValue(underTest.actions)
 
-            assertThat(underTest.isActive).isFalse()
             assertThat(actions).isEmpty()
         }
 
@@ -66,7 +64,6 @@
             underTest.activateIn(testScope)
             runCurrent()
 
-            assertThat(underTest.isActive).isTrue()
             assertThat(actions).isEmpty()
         }
 
@@ -76,7 +73,6 @@
             val actions by collectLastValue(underTest.actions)
             underTest.activateIn(testScope)
             runCurrent()
-            assertThat(underTest.isActive).isTrue()
 
             val expected1 =
                 mapOf(
@@ -116,7 +112,6 @@
 
             job.cancel()
             runCurrent()
-            assertThat(underTest.isActive).isFalse()
             assertThat(actions).isEmpty()
         }
 
@@ -127,7 +122,7 @@
         override suspend fun hydrateActions(
             setActions: (Map<UserAction, UserActionResult>) -> Unit,
         ) {
-            upstream.collectLatest { setActions(it) }
+            upstream.collect { setActions(it) }
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index f856c55..3558f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.sceneContainerConfig
-import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -49,6 +48,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
@@ -113,11 +113,6 @@
         }
 
     @Test
-    fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
-    }
-
-    @Test
     fun sceneTransition() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
@@ -237,7 +232,7 @@
             sceneInteractor.setVisible(false, "reason")
             runCurrent()
             assertThat(underTest.isVisible).isFalse()
-            sceneInteractor.onRemoteUserInteractionStarted("reason")
+            sceneInteractor.onRemoteUserInputStarted("reason")
             runCurrent()
             assertThat(underTest.isVisible).isTrue()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
index 6a88664..8b97739 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -83,4 +85,69 @@
             )
             assertThat(isKeyguardOccluded).isFalse()
         }
+
+    @Test
+    fun transitionFromOccludedToDreamingTransitionRemainsTrue() =
+        testScope.runTest {
+            val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
+            assertThat(isKeyguardOccluded).isFalse()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.DREAMING,
+                        value = 0f,
+                        transitionState = TransitionState.STARTED,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.DREAMING,
+                        value = 0.5f,
+                        transitionState = TransitionState.RUNNING,
+                    ),
+                ),
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).isFalse()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DREAMING,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                ),
+            )
+            assertThat(isKeyguardOccluded).isTrue()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.DREAMING,
+                        to = KeyguardState.OCCLUDED,
+                        value = 0f,
+                        transitionState = TransitionState.STARTED,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.DREAMING,
+                        to = KeyguardState.OCCLUDED,
+                        value = 0.5f,
+                        transitionState = TransitionState.RUNNING,
+                    ),
+                ),
+                testScope,
+            )
+            assertThat(isKeyguardOccluded).isTrue()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.OCCLUDED,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                ),
+            )
+            assertThat(isKeyguardOccluded).isTrue()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
index 06a02c6..a931e65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
@@ -24,6 +24,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -87,7 +88,10 @@
                 AuthenticationMethodModel.Pin
             )
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -102,7 +106,10 @@
             )
             setDeviceEntered(true)
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -117,7 +124,10 @@
             )
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -133,7 +143,10 @@
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -150,7 +163,10 @@
             runCurrent()
             sceneInteractor.changeScene(Scenes.Gone, "reason")
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -182,7 +198,11 @@
             overrideResource(R.bool.config_use_split_notification_shade, true)
             kosmos.shadeStartable.start()
             val actions by collectLastValue(underTest.actions)
-            assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
+                .isNull()
         }
 
     @Test
@@ -191,7 +211,10 @@
             overrideResource(R.bool.config_use_split_notification_shade, false)
             kosmos.shadeStartable.start()
             val actions by collectLastValue(underTest.actions)
-            assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene)
+            assertThat(
+                    (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene)
+                        ?.toScene
+                )
                 .isEqualTo(Scenes.QuickSettings)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 14134cc..cea8857 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -18,11 +18,11 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
-import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -31,7 +31,6 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.testKosmos
@@ -44,7 +43,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+@EnableSceneContainer
 class HeadsUpNotificationInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
new file mode 100644
index 0000000..a310ef4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
+import com.android.systemui.statusbar.notification.row.shared.IconModel
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(RichOngoingNotificationFlag.FLAG_NAME)
+class EnRouteViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val repository = kosmos.fakeNotificationRowRepository
+
+    private var contentModel: EnRouteContentModel?
+        get() = repository.richOngoingContentModel.value as? EnRouteContentModel
+        set(value) {
+            repository.richOngoingContentModel.value = value
+        }
+
+    private lateinit var underTest: EnRouteViewModel
+
+    @Before
+    fun setup() {
+        underTest = kosmos.getEnRouteViewModel(repository)
+    }
+
+    @Test
+    fun viewModelShowsContent() =
+        testScope.runTest {
+            val title by collectLastValue(underTest.title)
+            val text by collectLastValue(underTest.text)
+            contentModel =
+                exampleEnRouteContent(
+                    title = "Example EnRoute Title",
+                    text = "Example EnRoute Text",
+                )
+            assertThat(title).isEqualTo("Example EnRoute Title")
+            assertThat(text).isEqualTo("Example EnRoute Text")
+        }
+
+    private fun exampleEnRouteContent(
+        icon: IconModel = mock(),
+        title: CharSequence = "example text",
+        text: CharSequence = "example title",
+    ) =
+        EnRouteContentModel(
+            smallIcon = icon,
+            title = title,
+            text = text,
+        )
+}
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 1356e93..06a2c5a 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
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 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
@@ -84,4 +85,48 @@
                 )
             )
         }
+
+    @Test
+    fun shouldCloseGuts_userInputOngoing_currentGestureInGuts() =
+        testScope.runTest {
+            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)
+
+            kosmos.sceneInteractor.onSceneContainerUserInputStarted()
+            underTest.setCurrentGestureInGuts(true)
+
+            assertThat(shouldCloseGuts).isFalse()
+        }
+
+    @Test
+    fun shouldCloseGuts_userInputOngoing_currentGestureNotInGuts() =
+        testScope.runTest {
+            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)
+
+            kosmos.sceneInteractor.onSceneContainerUserInputStarted()
+            underTest.setCurrentGestureInGuts(false)
+
+            assertThat(shouldCloseGuts).isTrue()
+        }
+
+    @Test
+    fun shouldCloseGuts_userInputNotOngoing_currentGestureInGuts() =
+        testScope.runTest {
+            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)
+
+            kosmos.sceneInteractor.onUserInputFinished()
+            underTest.setCurrentGestureInGuts(true)
+
+            assertThat(shouldCloseGuts).isFalse()
+        }
+
+    @Test
+    fun shouldCloseGuts_userInputNotOngoing_currentGestureNotInGuts() =
+        testScope.runTest {
+            val shouldCloseGuts by collectLastValue(underTest.shouldCloseGuts)
+
+            kosmos.sceneInteractor.onUserInputFinished()
+            underTest.setCurrentGestureInGuts(false)
+
+            assertThat(shouldCloseGuts).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index f96cf10..840aa92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.notification.data.repository.updateNotificationPolicy
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -41,7 +42,6 @@
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
@@ -535,7 +535,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun pinnedHeadsUpRows_filtersForPinnedItems() =
         testScope.runTest {
             val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
@@ -576,7 +576,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun hasPinnedHeadsUpRows_true() =
         testScope.runTest {
             val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -591,7 +591,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun hasPinnedHeadsUpRows_false() =
         testScope.runTest {
             val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -606,7 +606,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun topHeadsUpRow_emptyList_null() =
         testScope.runTest {
             val topHeadsUpRow by collectLastValue(underTest.topHeadsUpRow)
@@ -618,7 +618,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun headsUpAnimationsEnabled_true() =
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -631,7 +631,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun headsUpAnimationsEnabled_keyguardShowing_true() =
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 733cac9..3f97f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -42,10 +42,14 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -56,6 +60,10 @@
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.shade.shadeTestUtil
@@ -66,6 +74,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -295,34 +304,47 @@
 
             // Start transitioning to glanceable hub
             val progress = 0.6f
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = 0f,
-                )
+            kosmos.setTransition(
+                sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        value = 0f,
+                    )
             )
+
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = progress,
-                )
+            kosmos.setTransition(
+                sceneTransition =
+                    Transition(
+                        from = Scenes.Lockscreen,
+                        to = Scenes.Communal,
+                        progress = flowOf(progress)
+                    ),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.RUNNING,
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        value = progress,
+                    )
             )
+
             runCurrent()
             assertThat(alpha).isIn(Range.closed(0f, 1f))
 
             // Finish transition to glanceable hub
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.FINISHED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = 1f,
-                )
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.FINISHED,
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        value = 1f,
+                    )
             )
             assertThat(alpha).isEqualTo(0f)
 
@@ -348,35 +370,46 @@
 
             // Start transitioning to glanceable hub
             val progress = 0.6f
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = 0f,
-                )
+            kosmos.setTransition(
+                sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                        from = DREAMING,
+                        to = GLANCEABLE_HUB,
+                        value = 0f,
+                    )
             )
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.RUNNING,
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = progress,
-                )
+            kosmos.setTransition(
+                sceneTransition =
+                    Transition(
+                        from = Scenes.Lockscreen,
+                        to = Scenes.Communal,
+                        progress = flowOf(progress)
+                    ),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.RUNNING,
+                        from = DREAMING,
+                        to = GLANCEABLE_HUB,
+                        value = progress,
+                    )
             )
             runCurrent()
             // Keep notifications hidden during the transition from dream to hub
             assertThat(alpha).isEqualTo(0)
 
             // Finish transition to glanceable hub
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.FINISHED,
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.GLANCEABLE_HUB,
-                    value = 1f,
-                )
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.FINISHED,
+                        from = DREAMING,
+                        to = GLANCEABLE_HUB,
+                        value = 1f,
+                    )
             )
             assertThat(alpha).isEqualTo(0f)
         }
@@ -400,35 +433,47 @@
         testScope.runTest {
             val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(from = LOCKSCREEN, to = GONE)
             )
             assertThat(isOnLockscreen).isFalse()
 
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(from = GONE, to = LOCKSCREEN)
+            )
+            assertThat(isOnLockscreen).isTrue()
             // While progressing from lockscreen, should still be true
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
-                    value = 0.8f,
-                    transitionState = TransitionState.RUNNING
-                )
+            kosmos.setTransition(
+                sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Gone),
+                stateTransition =
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GONE,
+                        value = 0.8f,
+                        transitionState = TransitionState.RUNNING
+                    )
             )
             assertThat(isOnLockscreen).isTrue()
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition =
+                    TransitionStep(
+                        from = GONE,
+                        to = LOCKSCREEN,
+                    )
             )
             assertThat(isOnLockscreen).isTrue()
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.PRIMARY_BOUNCER,
-                testScope,
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Bouncer),
+                stateTransition =
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = PRIMARY_BOUNCER,
+                    )
             )
             assertThat(isOnLockscreen).isTrue()
         }
@@ -442,8 +487,8 @@
             shadeTestUtil.setLockscreenShadeExpansion(0f)
             shadeTestUtil.setQsExpansion(0f)
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.OCCLUDED,
+                from = LOCKSCREEN,
+                to = OCCLUDED,
                 testScope,
             )
             assertThat(isOnLockscreenWithoutShade).isFalse()
@@ -480,11 +525,15 @@
             assertThat(isOnGlanceableHubWithoutShade).isFalse()
 
             // Move to glanceable hub
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope = this
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                    )
             )
+
             assertThat(isOnGlanceableHubWithoutShade).isTrue()
 
             // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -502,6 +551,14 @@
 
             shadeTestUtil.setQsExpansion(0f)
             shadeTestUtil.setLockscreenShadeExpansion(0f)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                    )
+            )
             assertThat(isOnGlanceableHubWithoutShade).isTrue()
         }
 
@@ -808,8 +865,8 @@
             // GONE transition gets to 90% complete
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
+                    from = LOCKSCREEN,
+                    to = GONE,
                     transitionState = TransitionState.STARTED,
                     value = 0f,
                 )
@@ -817,8 +874,8 @@
             runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.GONE,
+                    from = LOCKSCREEN,
+                    to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
                 )
@@ -843,8 +900,8 @@
             // OCCLUDED transition gets to 90% complete
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.OCCLUDED,
+                    from = LOCKSCREEN,
+                    to = OCCLUDED,
                     transitionState = TransitionState.STARTED,
                     value = 0f,
                 )
@@ -852,8 +909,8 @@
             runCurrent()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.OCCLUDED,
+                    from = LOCKSCREEN,
+                    to = OCCLUDED,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
                 )
@@ -877,8 +934,8 @@
             showLockscreen()
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
+                from = LOCKSCREEN,
+                to = GONE,
                 testScope
             )
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
@@ -922,8 +979,8 @@
 
             // ... then user hits power to go to AOD
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.AOD,
+                from = LOCKSCREEN,
+                to = AOD,
                 testScope,
             )
             // ... followed by a shade collapse
@@ -945,7 +1002,7 @@
             // PRIMARY_BOUNCER->GONE transition is started
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.STARTED,
                     value = 0f,
@@ -956,7 +1013,7 @@
             // PRIMARY_BOUNCER->GONE transition running
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.1f,
@@ -967,7 +1024,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
@@ -979,7 +1036,7 @@
             hideCommunalScene()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.FINISHED,
                     value = 1f
@@ -1003,7 +1060,7 @@
             // PRIMARY_BOUNCER->GONE transition is started
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.STARTED,
                 )
@@ -1013,7 +1070,7 @@
             // PRIMARY_BOUNCER->GONE transition running
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.1f,
@@ -1024,7 +1081,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
@@ -1035,7 +1092,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.PRIMARY_BOUNCER,
+                    from = PRIMARY_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.FINISHED,
                     value = 1f
@@ -1058,7 +1115,7 @@
             // ALTERNATE_BOUNCER->GONE transition is started
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.STARTED,
                     value = 0f,
@@ -1069,7 +1126,7 @@
             // ALTERNATE_BOUNCER->GONE transition running
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.1f,
@@ -1080,7 +1137,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
@@ -1092,7 +1149,7 @@
             hideCommunalScene()
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.FINISHED,
                     value = 1f
@@ -1116,7 +1173,7 @@
             // ALTERNATE_BOUNCER->GONE transition is started
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.STARTED,
                 )
@@ -1126,7 +1183,7 @@
             // ALTERNATE_BOUNCER->GONE transition running
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.1f,
@@ -1137,7 +1194,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.RUNNING,
                     value = 0.9f,
@@ -1148,7 +1205,7 @@
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    from = ALTERNATE_BOUNCER,
                     to = GONE,
                     transitionState = TransitionState.FINISHED,
                     value = 1f
@@ -1165,8 +1222,8 @@
         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.AOD,
-            to = KeyguardState.LOCKSCREEN,
+            from = AOD,
+            to = LOCKSCREEN,
             testScope,
         )
     }
@@ -1178,8 +1235,8 @@
         keyguardRepository.setDreaming(true)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.DREAMING,
+            from = LOCKSCREEN,
+            to = DREAMING,
             testScope,
         )
     }
@@ -1191,8 +1248,8 @@
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.AOD,
-            to = KeyguardState.LOCKSCREEN,
+            from = AOD,
+            to = LOCKSCREEN,
             testScope,
         )
     }
@@ -1204,8 +1261,8 @@
         keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.AOD,
-            to = KeyguardState.LOCKSCREEN,
+            from = AOD,
+            to = LOCKSCREEN,
             testScope,
         )
     }
@@ -1219,8 +1276,8 @@
         kosmos.keyguardBouncerRepository.setPrimaryShow(true)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.GLANCEABLE_HUB,
-            to = KeyguardState.PRIMARY_BOUNCER,
+            from = GLANCEABLE_HUB,
+            to = PRIMARY_BOUNCER,
             testScope,
         )
     }
@@ -1234,8 +1291,8 @@
         kosmos.keyguardBouncerRepository.setPrimaryShow(false)
         runCurrent()
         keyguardTransitionRepository.sendTransitionSteps(
-            from = KeyguardState.GLANCEABLE_HUB,
-            to = KeyguardState.ALTERNATE_BOUNCER,
+            from = GLANCEABLE_HUB,
+            to = ALTERNATE_BOUNCER,
             testScope,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index ca106fa..469a7bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -36,7 +37,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -469,9 +469,9 @@
         @get:Parameters(name = "{0}")
         val flags: List<FlagsParameterization>
             get() = buildList {
-                addAll(FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME))
                 addAll(
-                    FlagsParameterization.allCombinationsOf(NotificationsHeadsUpRefactor.FLAG_NAME)
+                    FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
+                        .andSceneContainer()
                 )
             }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index d2bc54e0..33f379d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenIconLoader
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -34,10 +35,12 @@
 import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 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.Mockito.clearInvocations
@@ -64,6 +67,11 @@
             mockDialogEventLogger
         )
 
+    @Before
+    fun setUp() {
+        ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService()))
+    }
+
     @Test
     fun tiles_filtersOutUserDisabledModes() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 51a70bd..fe6c741 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
@@ -157,6 +158,10 @@
     @Test
     fun testDumpingState() =
         test({
+            testableResources.addOverride(R.bool.volume_panel_is_large_screen, false)
+            testableResources.overrideConfiguration(
+                Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+            )
             componentByKey =
                 mapOf(
                     COMPONENT_1 to mockVolumePanelUiComponentProvider,
diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
new file mode 100644
index 0000000..59cfecc
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:minHeight="@*android:dimen/notification_headerless_min_height"
+    android:tag="enroute"
+    >
+
+    <include layout="@*android:layout/notification_template_material_base" />
+
+</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index dfdb15d..7251f03 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth sal môreoggend aanskakel"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deel oudio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deel tans oudio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"voer instellings vir oudiodeling in"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterykrag"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Klaar"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Instellings"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aan"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Op • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Af"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Stel op"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Bestuur in instellings"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goeie toestand"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding is beskikbaar"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliet-SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Pret vir party mense, maar nie vir almal nie"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Stelsel-UI-ontvanger gee jou ekstra maniere om die Android-gebruikerkoppelvlak in te stel en te pasmaak. Hierdie eksperimentele kenmerke kan in toekomstige uitreikings verander, breek of verdwyn. Gaan versigtig voort."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9babced..54fb216d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ብሉቱዝ ነገ ጠዋት ይበራል"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ኦዲዮ አጋራ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ኦዲዮ በማጋራት ላይ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"የድምፅ ማጋሪያ ቅንብሮች አስገባ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ባትሪ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ተከናውኗል"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ቅንብሮች"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"በርቷል"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"በርቷል • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ጠፍቷል"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"አዋቅር"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"በቅንብሮች ውስጥ አስተዳድር"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ሳተላይት፣ ጥሩ ግንኙነት"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ሳተላይት፣ ግንኙነት አለ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ሳተላይት ኤስኦኤስ"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"የስራ መገለጫ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ለአንዳንዶች አስደሳች ቢሆንም ለሁሉም አይደለም"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"የስርዓት በይነገጽ መቃኛ የAndroid ተጠቃሚ በይነገጹን የሚነካኩበት እና የሚያበጁበት ተጨማሪ መንገዶች ይሰጠዎታል። እነዚህ የሙከራ ባህሪዎች ወደፊት በሚኖሩ ልቀቶች ላይ ሊለወጡ፣ ሊሰበሩ ወይም ሊጠፉ ይችላሉ። ከጥንቃቄ ጋር ወደፊት ይቀጥሉ።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 3b867be..6866ed7 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"سيتم تفعيل البلوتوث صباح الغد"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"مشاركة الصوت"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"جارٍ مشاركة الصوت"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"أدخِل إعدادات ميزة \"مشاركة الصوت\""</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"مستوى طاقة البطارية <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"تم"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"الإعدادات"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"مفعَّل"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"مفعّل • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"غير مفعَّل"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"إعداد"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"الإدارة في الإعدادات"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"قمر صناعي، الاتصال جيد"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"قمر صناعي، الاتصال متوفّر"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"اتصالات الطوارئ بالقمر الصناعي"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ملف العمل"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"متعة للبعض وليس للجميع"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏توفر لك أداة ضبط واجهة مستخدم النظام طرقًا إضافية لتعديل واجهة مستخدم Android وتخصيصها. ويمكن أن تطرأ تغييرات على هذه الميزات التجريبية أو يمكن أن تتعطل هذه الميزات أو تختفي في الإصدارات المستقبلية. عليك متابعة الاستخدام مع توخي الحذر."</string>
@@ -845,7 +847,7 @@
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"الإدخال"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"التبديل إلى اللغة التالية"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"التبديل إلى اللغة السابقة"</string>
-    <string name="input_access_emoji" msgid="8105642858900406351">"الوصول إلى الرموز التعبيرية"</string>
+    <string name="input_access_emoji" msgid="8105642858900406351">"الوصول إلى رموز الإيموجي"</string>
     <string name="input_access_voice_typing" msgid="7291201476395326141">"الوصول إلى ميزة \"الكتابة بالصوت\""</string>
     <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"التطبيقات"</string>
     <string name="keyboard_shortcut_group_applications_assist" msgid="6772492350416591448">"‏مساعد Google"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 6dbfe67..0c8ef43 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"কাইলৈ পুৱা ব্লুটুথ অন হ’ব"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"অডিঅ’ শ্বেয়াৰ কৰক"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"অডিঅ’ শ্বেয়াৰ কৰি থকা হৈছে"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"অডিঅ’ শ্বেয়াৰ কৰাৰ ছেটিঙলৈ যাওক"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"কৰা হ’ল"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ছেটিং"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"অন আছে"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"অন আছে • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"অফ আছে"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ছেট আপ কৰক"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ছেটিঙত পৰিচালনা কৰক"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"উপগ্ৰহ, ভাল সংযোগ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"উপগ্ৰহ, সংযোগ উপলব্ধ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"উপগ্ৰহ SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"কিছুমানৰ বাবে আমোদজনক হয় কিন্তু সকলোৰে বাবে নহয়"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tunerএ আপোনাক Android ব্যৱহাৰকাৰী ইণ্টাৰফেইচ সলনি কৰিবলৈ আৰু নিজৰ উপযোগিতা অনুসৰি ব্যৱহাৰ কৰিবলৈ অতিৰিক্ত সুবিধা প্ৰদান কৰে। এই পৰীক্ষামূলক সুবিধাসমূহ সলনি হ\'ব পাৰে, সেইবোৰে কাম নকৰিব পাৰে বা আগন্তুক সংস্কৰণসমূহত সেইবোৰ অন্তৰ্ভুক্ত কৰা নহ’ব পাৰে। সাৱধানেৰে আগবাঢ়ক।"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index ee7872e..df3ecf7 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth sabah səhər aktiv ediləcək"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Audio paylaşın"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audio paylaşılır"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"audio paylaşma ayarlarına daxil olun"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batareya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Hazırdır"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ayarlar"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aktiv"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Aktiv • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Deaktiv"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Ayarlayın"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Ayarlarda idarə edin"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Peyk, bağlantı yaxşıdır"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Peyk, bağlantı var"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Təcili peyk bağlantısı"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Hamı üçün deyil, bəziləri üçün əyləncəli"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android istifadəçi interfeysini dəyişdirmək və fərdiləşdirmək üçün Sizə ekstra yollar təklif edir."</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 0d6504f..9198710 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth će se uključiti sutra ujutru"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deli zvuk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deli se zvuk"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"uđite u podešavanja deljenja zvuka"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gotovo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Podešavanja"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Uključeno"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Uklj. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Isključeno"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Podesi"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Upravljajte u podešavanjima"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, veza je dobra"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć preko satelita"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Tjuner za korisnički interfejs sistema vam pruža dodatne načine za podešavanje i prilagođavanje Android korisničkog interfejsa. Ove eksperimentalne funkcije mogu da se promene, otkažu ili nestanu u budućim izdanjima. Budite oprezni."</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 4fae4f5..c5c2912 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth уключыцца заўтра раніцай"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Абагуліць аўдыя"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Ідзе абагульванне аўдыя"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"адкрыць налады абагульвання аўдыя"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Гатова"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Налады"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Уключана"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Уключана • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Выключана"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Наладзіць"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Адкрыць налады"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спадарожнікавая сувязь, добрае падключэнне"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спадарожнікавая сувязь, падключэнне даступнае"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Экстраннае спадарожнікавае падключэнне"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Працоўны профіль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Цікава для некаторых, але не для ўсіх"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Наладка сістэмнага інтэрфейсу карыстальніка дае вам дадатковыя спосабы наладжвання і дапасоўвання карыстальніцкага інтэрфейсу Android. Гэтыя эксперыментальныя функцыі могуць змяніцца, перастаць працаваць або знікнуць у будучых версіях. Карыстайцеся з асцярожнасцю."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index dee6682..58f492e 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ще се включи утре сутрин"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Споделяне на звука"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Звукът се споделя"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"отваряне на настройките за споделяне на звука"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -390,7 +389,7 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Запис на екрана"</string>
     <string name="performance" msgid="6552785217174378320">"Ефективност"</string>
     <string name="user_interface" msgid="3712869377953950887">"Потребителски интерфейс"</string>
-    <string name="thermal" msgid="6758074791325414831">"Термално"</string>
+    <string name="thermal" msgid="6758074791325414831">"Температура"</string>
     <string name="custom" msgid="3337456985275158299">"Персонализирано"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Настройки за персонализираната следа"</string>
     <string name="restore_default" msgid="5259420807486239755">"Възстановяване на стандартната настройка"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Готово"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Настройки"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Вкл."</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Вкл. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Изкл."</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Настройване"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Управление от настройките"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, добра връзка"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, налице е връзка"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS чрез сателит"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Потребителски профил в Work"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забавно – но не за всички"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Тунерът на системния потребителски интерфейс ви предоставя допълнителни възможности за прецизиране и персонализиране на практическата работа с Android. Тези експериментални функции може да се променят, повредят или да изчезнат в бъдещите версии. Действайте внимателно."</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index ebef35a..c9e24f0 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ব্লুটুথ আগামীকাল সকালে চালু হয়ে যাবে"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"অডিও শেয়ার করুন"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"অডিও শেয়ার করা হচ্ছে"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"অডিও শেয়ার করার সেটিংসে যান"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"চার্জ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"হয়ে গেছে"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"সেটিংস"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"চালু আছে"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"চালু আছে • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"বন্ধ আছে"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"সেট-আপ করুন"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"সেটিংসে গিয়ে ম্যানেজ করুন"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"স্যাটেলাইট, ভালো কানেকশন"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"স্যাটেলাইট, কানেকশন উপলভ্য আছে"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"স্যাটেলাইট SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"কাজের প্রোফাইল"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"কিছু ব্যক্তির জন্য মজাদার কিন্তু সকলের জন্য নয়"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"এই পরীক্ষামূলক বৈশিষ্ট্যগুলি ভবিষ্যতের সংস্করণগুলির মধ্যে পরিবর্তিত, বিভাজিত এবং অদৃশ্য হয়ে যেতে পারে৷ সাবধানতার সাথে এগিয়ে যান৷ সিস্টেম UI টিউনার আপনাকে Android ব্যবহারকারী ইন্টারফেসের সূক্ষ্ম সমন্বয় এবং কাস্টমাইজ করার অতিরিক্ত উপায়গুলি প্রদান করে৷"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index e1a632a..8949567 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth će se uključiti sutra ujutro"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Dijeli zvuk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Dijeljenje zvuka"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ulazak u postavke dijeljenja zvuka"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -381,8 +380,8 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Snimanje ekrana"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Započnite"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavite"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Zabilježite problem"</string>
-    <string name="qs_record_issue_start" msgid="2979831312582567056">"Pokrenite"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Snimite problem"</string>
+    <string name="qs_record_issue_start" msgid="2979831312582567056">"Pokreni"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Zaustavite"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Izvještaj o grešci"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Koji dio uređaja je imao problem?"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gotovo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Postavke"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Uključeno"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Uključeno • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Isključeno"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Postavite"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Upravljajte opcijom u postavkama"</string>
@@ -673,7 +673,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Prostorni zvuk"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Isključi"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fiksno"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Praćenje glave"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Praćenje položaja glave"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da promijenite način rada zvuka zvona"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključite zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključite zvuk"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć putem satelita"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Radni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Podešavač za korisnički interfejs sistema vam omogućava dodatne načine da podesite i prilagodite Androidov interfejs. Ove eksperimentalne funkcije se u budućim verzijama mogu mijenjati, kvariti ili nestati. Budite oprezni."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index e985e65..74cce98 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth s\'activarà demà al matí"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Comparteix l\'àudio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"S\'està compartint l\'àudio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"introduir la configuració de compartició d\'àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Fet"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configuració"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activat"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activat • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desactivat"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configura"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gestiona a la configuració"</string>
@@ -506,7 +506,7 @@
     <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"suprimeix el widget"</string>
     <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"col·loca el widget seleccionat"</string>
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Widgets de la pantalla de bloqueig"</string>
-    <string name="communal_widget_picker_description" msgid="490515450110487871">"Tothom pot veure widgets a la pantalla de bloqueig, fins i tot amb la tauleta bloquejada."</string>
+    <string name="communal_widget_picker_description" msgid="490515450110487871">"Tothom pot veure els widgets de la teva pantalla de bloqueig, fins i tot quan la tauleta està bloquejada."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"desselecciona el widget"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets de la pantalla de bloqueig"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per obrir una aplicació utilitzant un widget, necessitaràs verificar la teva identitat. També has de tenir en compte que qualsevol persona pot veure els widgets, fins i tot quan la tauleta està bloquejada. És possible que alguns widgets no estiguin pensats per a la pantalla de bloqueig i que no sigui segur afegir-los-hi."</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satèl·lit, bona connexió"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satèl·lit, connexió disponible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS per satèl·lit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de treball"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversió per a uns quants, però no per a tothom"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"El Personalitzador d\'interfície d\'usuari presenta opcions addicionals per canviar i personalitzar la interfície d\'usuari d\'Android. És possible que aquestes funcions experimentals canviïn, deixin de funcionar o desapareguin en versions futures. Continua amb precaució."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index df5447e..8c0571e 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno."</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Sdílet zvuk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Zvuk se sdílí"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"přejdete do nastavení sdílení zvuku"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Hotovo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Nastavení"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Zapnuto"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Zapnuto • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Vypnuto"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Nastavit"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Spravovat v nastavení"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobré připojení"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, připojení je k dispozici"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS přes satelit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovní profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zábava, která není pro každého"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Nástroj na ladění uživatelského rozhraní systému vám nabízí další způsoby, jak si vyladit a přizpůsobit uživatelské rozhraní Android. Tyto experimentální funkce mohou v dalších verzích chybět, nefungovat nebo být změněny. Postupujte proto prosím opatrně."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 813329b..9b72478 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth aktiveres i morgen tidlig"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Del lyd"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deler lyd"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"angive indstillinger for lyddeling"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Udfør"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Indstillinger"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Til"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Til • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Fra"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Konfigurer"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Administrer i indstillingerne"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit – god forbindelse"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit – forbindelsen er tilgængelig"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-meldinger via satellit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbejdsprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Sjovt for nogle, men ikke for alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner giver dig flere muligheder for at justere og tilpasse Android-brugerfladen. Disse eksperimentelle funktioner kan ændres, gå i stykker eller forsvinde i fremtidige udgivelser. Vær forsigtig, hvis du fortsætter."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 4b87dd0..7bfdf67 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth wird morgen früh aktiviert"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Audioinhalte freigeben"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioinhalte werden freigegeben"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"Einstellungen für die Audiofreigabe eingeben"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Bildschirmaufzeichnung"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Beenden"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Problem aufnehmen"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Problem aufzeichnen"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Starten"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Aufnahme beenden"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Fehlerbericht"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Fertig"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Einstellungen"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"An"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"An • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Aus"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Einrichten"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"In den Einstellungen verwalten"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, Verbindung gut"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, Verbindung verfügbar"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Notruf über Satellit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbeitsprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Für einige ein Vergnügen, aber nicht für alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Mit System UI Tuner erhältst du zusätzliche Möglichkeiten, die Android-Benutzeroberfläche anzupassen. Achtung: Diese Testfunktionen können sich ändern, abstürzen oder in zukünftigen Versionen verschwinden."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 3cddb8b..ed17959 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Το Bluetooth θα ενεργοποιηθεί αύριο το πρωί"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Κοινή χρήση ήχου"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Κοινή χρήση ήχου σε εξέλιξη"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"είσοδο στις ρυθμίσεις κοινής χρήσης ήχου"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Μπαταρία <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Εγγραφή οθόνης"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Έναρξη"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Διακοπή"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Εγγραφή προβλήματος"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Εγγραφή προβλ."</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Έναρξη"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Διακοπή"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Αναφορά σφάλματος"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Τέλος"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ρυθμίσεις"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ενεργό"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ενεργή • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Ανενεργό"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Ρύθμιση"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Διαχείριση στις ρυθμίσεις"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Δορυφορική, καλή σύνδεση"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Δορυφορική, διαθέσιμη σύνδεση"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Δορυφορικό SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Προφίλ εργασίας"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Διασκέδαση για ορισμένους, αλλά όχι για όλους"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Το System UI Tuner σάς προσφέρει επιπλέον τρόπους για να τροποποιήσετε και να προσαρμόσετε τη διεπαφή χρήστη Android. Αυτές οι πειραματικές λειτουργίες ενδέχεται να τροποποιηθούν, να παρουσιάσουν σφάλματα ή να καταργηθούν σε μελλοντικές εκδόσεις. Συνεχίστε με προσοχή."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 90442b6..0565be8 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"enter audio sharing settings"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Done"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Settings"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"On"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"On • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Set up"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Manage in settings"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index d985b43..17642f7 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -435,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Done"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Settings"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"On"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"On • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Set up"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Manage in settings"</string>
@@ -717,6 +718,7 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Emergency calls or SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 90442b6..0565be8 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"enter audio sharing settings"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Done"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Settings"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"On"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"On • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Set up"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Manage in settings"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 90442b6..0565be8 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Share audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Sharing audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"enter audio sharing settings"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Done"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Settings"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"On"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"On • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Set up"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Manage in settings"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1d29f65..d31d328 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -435,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎Done‎‏‎‎‏‎"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎Settings‎‏‎‎‏‎"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎On‎‏‎‎‏‎"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎On • ‎‏‎‎‏‏‎<xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎Off‎‏‎‎‏‎"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎Set up‎‏‎‎‏‎"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎Manage in settings‎‏‎‎‏‎"</string>
@@ -717,6 +718,7 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎Satellite, good connection‎‏‎‎‏‎"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎Satellite, connection available‎‏‎‎‏‎"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎Satellite SOS‎‏‎‎‏‎"</string>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‎Emergency calls or SOS‎‏‎‎‏‎"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‎Work profile‎‏‎‎‏‎"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎Fun for some but not for all‎‏‎‎‏‎"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index a61f55a..53c38e2 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth se activará mañana a la mañana"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartir audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartiendo audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ingresar la configuración de uso compartido de audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Listo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configuración"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Sí • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desactivado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Administrar en configuración"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabajo"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión solo para algunas personas"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"El sintonizador de IU del sistema te brinda más formas para editar y personalizar la interfaz de usuario de Android. Estas funciones experimentales pueden cambiar, dejar de funcionar o no incluirse en futuras versiones. Procede con precaución."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 07fec5e..0bebccf 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth se activará mañana por la mañana"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartir audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartiendo audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"acceder a las opciones para compartir audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -381,18 +380,18 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Grabar pantalla"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Detener"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Problema de grabación"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Grabar problema"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Detener"</string>
-    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe errores"</string>
+    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe de errores"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"¿Qué parte de tu experiencia se ha visto afectada?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecciona el tipo de problema"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Grabar pantalla"</string>
     <string name="performance" msgid="6552785217174378320">"Rendimiento"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interfaz de usuario"</string>
     <string name="thermal" msgid="6758074791325414831">"Temperatura"</string>
-    <string name="custom" msgid="3337456985275158299">"Personalizado"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Ajustes de rastreo personalizados"</string>
+    <string name="custom" msgid="3337456985275158299">"Otro"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Ajustes de traza personalizados"</string>
     <string name="restore_default" msgid="5259420807486239755">"Restaurar ajustes predeterminados"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Modo Una mano"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Audífonos"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Hecho"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ajustes"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activado • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desactivado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gestionar en los ajustes"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabajo"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión solo para algunos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"El configurador de UI del sistema te ofrece otras formas de modificar y personalizar la interfaz de usuario de Android. Estas funciones experimentales pueden cambiar, fallar o desaparecer en futuras versiones. Te recomendamos que tengas cuidado."</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 67b2bd0..b2ffa80 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth lülitub sisse homme hommikul"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Jaga heli"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Heli jagamine"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"heli jagamise seadete sisestamiseks"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> akut"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Valmis"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Seaded"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Sees"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Sees • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Väljas"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Seadistamine"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Seadetes halamine"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliit, hea ühendus"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliit, ühendus on saadaval"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliit-SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Tööprofiil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kõik ei pruugi sellest rõõmu tunda"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Süsteemi kasutajaliidese tuuner pakub täiendavaid võimalusi Androidi kasutajaliidese muutmiseks ja kohandamiseks. Need katselised funktsioonid võivad muutuda, rikki minna või tulevastest versioonidest kaduda. Olge jätkamisel ettevaatlik."</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 0d6c178..d8cff19 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bihar goizean aktibatuko da Bluetootha"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Partekatu audioa"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioa partekatzen"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"audioa partekatzeko ezarpenetan sartzeko"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Pantaila-grabaketa"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Hasi"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Gelditu"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Arazo bat dago grabaketarekin"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Grabatu arazoa"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Hasi"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Gelditu"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Akatsen txostena"</string>
@@ -392,7 +391,7 @@
     <string name="user_interface" msgid="3712869377953950887">"Erabiltzaile-interfazea"</string>
     <string name="thermal" msgid="6758074791325414831">"Termikoa"</string>
     <string name="custom" msgid="3337456985275158299">"Pertsonalizatua"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Arrasto pertsonalizatuen ezarpenak"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Arrastoaren ezarpen pertsonalizatuak"</string>
     <string name="restore_default" msgid="5259420807486239755">"Leheneratu balio lehenetsia"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Esku bakarreko modua"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Entzumen-gailuak"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Eginda"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ezarpenak"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aktibatuta"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Aktibo • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desaktibatuta"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Konfiguratu"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Kudeatu ezarpenetan"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelitea, konexio ona"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelitea, konexioa erabilgarri"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelite bidezko SOS komunikazioa"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Laneko profila"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Dibertsioa batzuentzat, baina ez guztientzat"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistemaren erabiltzaile-interfazearen konfiguratzaileak Android erabiltzaile-interfazea moldatzeko eta pertsonalizatzeko modu gehiago eskaintzen dizkizu. Baliteke eginbide esperimental horiek hurrengo kaleratzeetan aldatuta, etenda edo desagertuta egotea. Kontuz erabili."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 1cfb3af..5b17c44 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"بلوتوث فردا صبح روشن خواهد شد"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"هم‌رسانی صدا"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"درحال هم‌رسانی صدا"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"وارد شدن به تنظیمات «اشتراک صدا»"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"تمام"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"تنظیمات"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"روشن"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"روشن • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"خاموش"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"راه‌اندازی"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"مدیریت در تنظیمات"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ماهواره، اتصال خوب است"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ماهواره، اتصال دردسترس است"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"درخواست کمک ماهواره‌ای"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"نمایه کاری"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"برای بعضی افراد سرگرم‌کننده است اما نه برای همه"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏«تنظیم‌کننده واسط کاربری سیستم» روش‌های بیشتری برای تنظیم دقیق و سفارشی کردن واسط کاربری Android در اختیار شما قرار می‌دهد. ممکن است این ویژگی‌های آزمایشی تغییر کنند، خراب شوند یا در نسخه‌های آینده جود نداشته باشند. با احتیاط ادامه دهید."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index b7467e6..9244bec 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -108,7 +108,7 @@
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Näytön tallennusta käsitellään"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Tallennetaanko näytön toimintaa?"</string>
-    <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Tallenna yksi sovellus"</string>
+    <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Tallenna yhdestä sovelluksesta"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tallenna koko näyttö"</string>
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kun tallennat koko näyttöä, kaikki näytöllä näkyvä sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kun tallennat sovellusta, kaikki sovelluksessa näkyvä tai toistettu sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
@@ -153,7 +153,7 @@
     <string name="issuerecord_title" msgid="286627115110121849">"Ongelman tallentaja"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Käsittely: Ongelman tallennus"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Ongelmankeräykseen liittyvä ilmoitus taustalla jatkuvasta toiminnasta"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Tallennusongelma"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Tallennetaan ongelmaa"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Jaa"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"Ongelman tallennus tallennettiin"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Näytä napauttamalla"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth menee päälle huomisaamuna"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Jaa audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audiota jaetaan"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"lisätäksesi audion jakamisasetukset"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Valmis"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Asetukset"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Päällä"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Päällä • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Pois päältä"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Ota käyttöön"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Muuta asetuksista"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliitti, hyvä yhteys"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliitti, yhteys saatavilla"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Työprofiili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Ei sovellu kaikkien käyttöön"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner antaa lisämahdollisuuksia Android-käyttöliittymän muokkaamiseen. Nämä kokeelliset ominaisuudet voivat muuttua, lakata toimimasta tai kadota milloin tahansa. Jatka omalla vastuullasi."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index a45c9ae..41305af 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -126,7 +126,7 @@
     <string name="screenrecord_stop_label" msgid="72699670052087989">"Arrêter"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Partager"</string>
     <string name="screenrecord_save_title" msgid="1886652605520893850">"Enregistrement sauvegardé"</string>
-    <string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
+    <string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez ceci pour afficher"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
     <string name="screenrecord_stop_dialog_title" msgid="8716193661764511095">"Arrêter l\'enregistrement?"</string>
@@ -155,8 +155,8 @@
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Notification continue pour une session de collecte d\'un problème"</string>
     <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Enregistrement du problème"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Partager"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"L\'enregistrement du problème a été enregistré"</string>
-    <string name="issuerecord_save_text" msgid="1205985304551521495">"Touchez ici pour afficher l\'enregistrement"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"Le problème a été enregistré"</string>
+    <string name="issuerecord_save_text" msgid="1205985304551521495">"Touchez ceci pour afficher l\'enregistrement"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"Erreur lors de l\'enregistrement du problème"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Erreur lors du démarrage de l\'enregistrement du problème"</string>
     <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Le Bluetooth s\'activera demain matin"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Partager l\'audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Partage de l\'audio en cours…"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"entrer les paramètres de partage audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Enregistrement d\'écran"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Démarrer"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Arrêter"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Rapporter le problème"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Enregistrer le problème"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Commencer"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Arrêter"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Rapport de bogue"</string>
@@ -390,9 +389,9 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Enregistrement écran"</string>
     <string name="performance" msgid="6552785217174378320">"Performance"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interface utilisateur"</string>
-    <string name="thermal" msgid="6758074791325414831">"Thermique"</string>
-    <string name="custom" msgid="3337456985275158299">"Personnalisé"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Paramètres personnalisés de la trace"</string>
+    <string name="thermal" msgid="6758074791325414831">"Chaleur"</string>
+    <string name="custom" msgid="3337456985275158299">"Type personnalisé"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Paramètres de traçage personnalisés"</string>
     <string name="restore_default" msgid="5259420807486239755">"Restaurer la valeur par défaut"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Mode Une main"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Appareils auditifs"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"OK"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Paramètres"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activé"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activé • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Désactivé"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurer"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gérer dans les paramètres"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite accessible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur d\'Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string>
@@ -1270,7 +1272,7 @@
     <string name="clipboard_edit_text_description" msgid="805254383912962103">"Modifier le texte copié"</string>
     <string name="clipboard_edit_image_description" msgid="8904857948976041306">"Modifier l\'image copiée"</string>
     <string name="clipboard_send_nearby_description" msgid="4629769637846717650">"Envoyer à un appareil à proximité"</string>
-    <string name="clipboard_text_hidden" msgid="7926899867471812305">"Touchez pour afficher"</string>
+    <string name="clipboard_text_hidden" msgid="7926899867471812305">"Touchez ceci pour afficher"</string>
     <string name="clipboard_text_copied" msgid="5100836834278976679">"Texte copié"</string>
     <string name="clipboard_image_copied" msgid="3793365360174328722">"Image copiée"</string>
     <string name="clipboard_content_copied" msgid="144452398567828145">"Contenu copié"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 12073b2..4df98cf 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -153,7 +153,7 @@
     <string name="issuerecord_title" msgid="286627115110121849">"Enregistreur de problèmes"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Enregistrement du problème"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Notification d\'activité en cours concernant la session d\'enregistrement d\'un problème"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Problème d\'enregistrement"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Enregistrement du problème"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Partager"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"Problème enregistré"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Appuyez pour afficher"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Le Bluetooth sera activé demain matin"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Partager le contenu audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audio partagé"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"accéder aux paramètres de partage audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batterie"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Enregistr. écran"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Démarrer"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Arrêter"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Enregistrer le problème"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Enreg. le problème"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Lancer"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Arrêter"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Rapport de bug"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"OK"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Paramètres"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activé"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activé • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Désactivé"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurer"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gérer dans les paramètres"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite disponible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index b0f4c41..1fe1242 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"O Bluetooth activarase mañá á mañá"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartir audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartindo audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"configurar o uso compartido de audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Gravar pantalla"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Deter"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Rexistrar problema"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Gravar problema"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Deter"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe de erros"</string>
@@ -391,7 +390,7 @@
     <string name="performance" msgid="6552785217174378320">"Rendemento"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interface de usuario"</string>
     <string name="thermal" msgid="6758074791325414831">"Térmico"</string>
-    <string name="custom" msgid="3337456985275158299">"Personalizada"</string>
+    <string name="custom" msgid="3337456985275158299">"Personalizado"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Configuración de rastrexo personalizada"</string>
     <string name="restore_default" msgid="5259420807486239755">"Restaurar configuración predeterminada"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Modo dunha soa man"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Feito"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configuración"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activo • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desactivado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Xestionar na configuración"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa conexión"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión dispoñible"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de traballo"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión só para algúns"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O configurador da IU do sistema ofréceche formas adicionais de modificar e personalizar a interface de usuario de Android. Estas funcións experimentais poden cambiar, interromperse ou desaparecer en futuras versións. Continúa con precaución."</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 0f36b0e..7ecc057 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"બ્લૂટૂથ આવતીકાલે સવારે ચાલુ થશે"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ઑડિયો શેર કરો"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ઑડિયો શેર કરી રહ્યાં છીએ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ઑડિયો શેરિંગ સેટિંગ દાખલ કરો"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
@@ -378,7 +377,7 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC અક્ષમ કરેલ છે"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC સક્ષમ કરેલ છે"</string>
-    <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"સ્ક્રીન રેકૉર્ડ"</string>
+    <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"સ્ક્રીન રેકોર્ડ"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"શરૂ કરો"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"રોકો"</string>
     <string name="qs_record_issue_label" msgid="8166290137285529059">"રેકોર્ડિંગમાં સમસ્યા"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"થઈ ગયું"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"સેટિંગ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ચાલુ"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ચાલુ • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"બંધ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"સેટઅપ કરો"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"સેટિંગમાં જઈને મેનેજ કરો"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"સૅટલાઇટ, સારું કનેક્શન"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"સૅટલાઇટ, કનેક્શન ઉપલબ્ધ છે"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ઇમર્જન્સી સૅટલાઇટ સહાય"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ઑફિસની પ્રોફાઇલ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"કેટલાક માટે મજા પરંતુ બધા માટે નહીં"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"સિસ્ટમ UI ટ્યૂનર તમને Android વપરાશકર્તા ઇન્ટરફેસને ટ્વીક અને કસ્ટમાઇઝ કરવાની વધારાની રીતો આપે છે. ભાવિ રીલિઝેસમાં આ પ્રાયોગિક સુવિધાઓ બદલાઈ, ભંગ અથવા અદૃશ્ય થઈ શકે છે. સાવધાની સાથે આગળ વધો."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e7cfe53..c6a5e57 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -150,10 +150,10 @@
     <string name="cast_to_other_device_stop_dialog_message_generic" msgid="4100272100480415076">"फ़िलहाल, आस-पास मौजूद किसी डिवाइस पर कास्ट किया जा रहा है"</string>
     <string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"कास्ट करना बंद करें"</string>
     <string name="close_dialog_button" msgid="4749497706540104133">"बंद करें"</string>
-    <string name="issuerecord_title" msgid="286627115110121849">"समस्या का डेटा सेव करने वाला टूल"</string>
+    <string name="issuerecord_title" msgid="286627115110121849">"समस्या रिकॉर्ड करने वाला टूल"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"समस्या का डेटा प्रोसेस हो रहा"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"समस्या का डेटा इकट्ठा करने के लिए बैकग्राउंड में जारी गतिविधि की सूचना"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"समस्या का डेटा इकट्ठा किया जा रहा है"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"समस्या रिकॉर्ड की जा रही है"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"शेयर करें"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"समस्या का डेटा सेव किया गया"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"देखने के लिए टैप करें"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ब्लूटूथ कल सुबह चालू होगा"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ऑडियो शेयर करें"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ऑडियो शेयर किया जा रहा है"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ऑडियो शेयर करने की सेटिंग जोड़ें"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बैटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -392,7 +391,7 @@
     <string name="user_interface" msgid="3712869377953950887">"यूज़र इंटरफ़ेस"</string>
     <string name="thermal" msgid="6758074791325414831">"थर्मल"</string>
     <string name="custom" msgid="3337456985275158299">"कस्टम"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"कस्टम ट्रेस सेटिंग"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"ट्रेस करने से जुड़ी कस्टम सेटिंग"</string>
     <string name="restore_default" msgid="5259420807486239755">"डिफ़ॉल्ट सेटिंग वापस लाएं"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"वन-हैंडेड मोड"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"कान की मशीनें"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"हो गया"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"सेटिंग"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"चालू है"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"<xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g> • पर"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"बंद है"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"सेट अप करें"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"सेटिंग में जाकर मैनेज करें"</string>
@@ -509,7 +509,7 @@
     <string name="communal_widget_picker_description" msgid="490515450110487871">"टैबलेट लॉक होने के बावजूद, कोई भी व्यक्ति इसकी लॉक स्क्रीन पर विजेट देख सकता है."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"विजेट से चुने हुए का निशान हटाएं"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लॉक स्क्रीन विजेट"</string>
-    <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"किसी विजेट से कोई ऐप्लिकेशन खोलने के लिए, आपको अपनी पहचान की पुष्टि करनी होगी. ध्यान रखें कि टैबलेट के लॉक होने पर भी कोई व्यक्ति विजेट देख सकता है. ऐसा हो सकता है कि कुछ विजेट लॉक स्क्रीन पर दिखाने के लिए न बने हों. इन्हें लॉक स्क्रीन पर जोड़ना असुरक्षित हो सकता है."</string>
+    <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"किसी विजेट से कोई ऐप्लिकेशन खोलने के लिए, आपको अपनी पहचान की पुष्टि करनी होगी. ध्यान रखें कि आपके टैबलेट के लॉक होने पर भी, कोई व्यक्ति विजेट देख सकता है. ऐसा हो सकता है कि कुछ विजेट, लॉक स्क्रीन पर दिखाने के लिए न बने हों. इन्हें लॉक स्क्रीन पर जोड़ना असुरक्षित हो सकता है."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ठीक है"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सैटलाइट कनेक्शन अच्छा है"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सैटलाइट कनेक्शन उपलब्ध है"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सैटलाइट एसओएस"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"वर्क प्रोफ़ाइल"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"कुछ के लिए मज़ेदार लेकिन सबके लिए नहीं"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर, आपको Android यूज़र इंटरफ़ेस में सुधार लाने और उसे अपनी पसंद के हिसाब से बदलने के कुछ और तरीके देता है. प्रयोग के तौर पर इस्तेमाल हो रहीं ये सुविधाएं आगे चल कर रिलीज़ की जा सकती हैं, रोकी जा सकती हैं या दिखाई देना बंद हो सकती हैं. सावधानी से आगे बढ़ें."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 196ae31f..2edf138 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth će se uključiti sutra ujutro"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Dijeli zvuk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Zajedničko slušanje"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"unesite postavke zajedničkog slušanja"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -381,12 +380,12 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Snimanje zaslona"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Početak"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavi"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Zabilježite poteškoću"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Snimite poteškoću"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Pokreni"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Zaustavi"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Izvješće o programskim pogreškama"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Na koji je dio doživljaja na uređaju to utjecalo?"</string>
-    <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Odaberite vrstu problema"</string>
+    <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Odaberite vrstu poteškoće"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Snimanje zaslona"</string>
     <string name="performance" msgid="6552785217174378320">"Izvedba"</string>
     <string name="user_interface" msgid="3712869377953950887">"Korisničko sučelje"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gotovo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Postavke"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Uključeno"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Uklj. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Isključeno"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Postavi"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Upravljajte u postavkama"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS putem satelita"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Ugađanje korisničkog sučelja sustava pruža vam dodatne načine za prilagodbu korisničkog sučelja Androida. Te se eksperimentalne značajke mogu promijeniti, prekinuti ili nestati u budućim izdanjima. Nastavite uz oprez."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index c3922d7..36bae57 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -153,7 +153,7 @@
     <string name="issuerecord_title" msgid="286627115110121849">"Problémafelvevő"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Problémafelvétel feldolgozása…"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Folyamatban lévő értesítés egy problémagyűjtési munkamenethez"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Problémafelvétel folyamatban…"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Probléma rögzítése folyamatban…"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Megosztás"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"Problémafelvétel mentve"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Koppintson a megtekintéshez"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"A Bluetooth holnap reggel bekapcsol"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Hang megosztása"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Hang megosztása…"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"a hangmegosztási beállítások megadásához"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Kész"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Beállítások"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Be"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Be • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Ki"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Beállítás"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"A Beállítások között kezelheti"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Műhold, jó kapcsolat"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Műhold, van rendelkezésre álló kapcsolat"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Műholdas SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Munkaprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Egyeseknek tetszik, másoknak nem"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"A Kezelőfelület-hangoló az Android felhasználói felületének szerkesztéséhez és testreszabásához nyújt további megoldásokat. Ezek a kísérleti funkciók változhatnak vagy megsérülhetnek a későbbi kiadásokban, illetve eltűnhetnek azokból. Körültekintően járjon el."</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 1499bca..24e9065 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -126,7 +126,7 @@
     <string name="screenrecord_stop_label" msgid="72699670052087989">"Կանգնեցնել"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Կիսվել"</string>
     <string name="screenrecord_save_title" msgid="1886652605520893850">"Էկրանի տեսագրությունը պահվեց"</string>
-    <string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
+    <string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք դիտելու համար"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
     <string name="screenrecord_stop_dialog_title" msgid="8716193661764511095">"Կանգնեցնե՞լ տեսագրումը"</string>
@@ -156,7 +156,7 @@
     <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Տեսագրում ենք խնդիրը"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Կիսվել"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"Տեսագրությունը պահվեց"</string>
-    <string name="issuerecord_save_text" msgid="1205985304551521495">"Հպեք՝ դիտելու համար"</string>
+    <string name="issuerecord_save_text" msgid="1205985304551521495">"Հպեք դիտելու համար"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"Չհաջողվեց պահել տեսագրությունը"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Չհաջողվեց սկսել տեսագրումը"</string>
     <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտակերպ"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-ը կմիանա վաղն առավոտյան"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Փոխանցել աուդիո"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Աուդիոյի փոխանցում"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"անցնել աուդիոյի փոխանցման կարգավորումներ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Էկրանի տեսագրում"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Սկսել"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Կանգնեցնել"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Ձայնագրել"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Խնդրի տեսագրում"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Սկսել"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Կանգնեցնել"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Հաղորդում սխալի մասին"</string>
@@ -390,7 +389,7 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Էկրանի տեսագրում"</string>
     <string name="performance" msgid="6552785217174378320">"Արդյունավետություն"</string>
     <string name="user_interface" msgid="3712869377953950887">"Օգտատիրական ինտերֆեյս"</string>
-    <string name="thermal" msgid="6758074791325414831">"Ջերմատեսիլ"</string>
+    <string name="thermal" msgid="6758074791325414831">"Տաքացում"</string>
     <string name="custom" msgid="3337456985275158299">"Հատուկ"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Հետագծման հատուկ կարգավորումներ"</string>
     <string name="restore_default" msgid="5259420807486239755">"Վերականգնել կանխադրված կարգավորումները"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Պատրաստ է"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Կարգավորումներ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Միացված է"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Միաց․ • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Անջատված է"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Կարգավորել"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Կառավարել կարգավորումներում"</string>
@@ -665,7 +665,7 @@
     <string name="stream_alarm_unavailable" msgid="4059817189292197839">"Հասանելի չէ․ «Չանհանգստացնել» ռեժիմը միացված է"</string>
     <string name="stream_media_unavailable" msgid="6823020894438959853">"Հասանելի չէ․ «Չանհանգստացնել» ռեժիմը միացված է"</string>
     <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s: Հպեք՝ ձայնը միացնելու համար:"</string>
-    <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s: Հպեք՝ թրթռումը միացնելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
+    <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s: Հպեք՝ թրթռոցը միացնելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s: Հպեք՝ ձայնն անջատելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s։ Հպեք՝ թրթռոցը միացնելու համար։"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s։ Հպեք՝ ձայնը անջատելու համար։"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Արբանյակային լավ կապ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Հասանելի է արբանյակային կապ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Աշխատանքային պրոֆիլ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Զվարճանք մեկ՝ որոշակի մարդու համար"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Համակարգի ՕՄ-ի կարգավորիչը հնարավորություն է տալիս հարմարեցնել Android-ի օգտատիրոջ միջերեսը: Այս փորձնական գործառույթները կարող են հետագա թողարկումների մեջ փոփոխվել, խափանվել կամ ընդհանրապես չհայտնվել: Եթե շարունակում եք, զգուշացեք:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index b580c81..d5f766b 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -123,7 +123,7 @@
     <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Merekam layar"</string>
     <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Merekam layar dan audio"</string>
     <string name="screenrecord_taps_label" msgid="1595690528298857649">"Tampilkan lokasi sentuhan pada layar"</string>
-    <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string>
+    <string name="screenrecord_stop_label" msgid="72699670052087989">"Berhenti"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"Bagikan"</string>
     <string name="screenrecord_save_title" msgid="1886652605520893850">"Rekaman layar disimpan"</string>
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dinyalakan besok pagi"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Bagikan audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Berbagi audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"masuk ke setelan berbagi audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -381,16 +380,16 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Perekam layar"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mulai"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Mencatat Masalah"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Rekam Masalah"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Mulai"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Berhenti"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Laporan Bug"</string>
-    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Hal apa yang terpengaruh?"</string>
+    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Apa jenis masalah yang Anda alami pada perangkat?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Pilih jenis masalah"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Perekaman layar"</string>
     <string name="performance" msgid="6552785217174378320">"Performa"</string>
     <string name="user_interface" msgid="3712869377953950887">"Antarmuka Pengguna"</string>
-    <string name="thermal" msgid="6758074791325414831">"Termal"</string>
+    <string name="thermal" msgid="6758074791325414831">"Panas"</string>
     <string name="custom" msgid="3337456985275158299">"Kustom"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Setelan Rekaman Aktivitas Kustom"</string>
     <string name="restore_default" msgid="5259420807486239755">"Pulihkan Default"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Selesai"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Setelan"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aktif"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Aktif • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Nonaktif"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Siapkan"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Kelola di setelan"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, koneksi baik"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, koneksi tersedia"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Tidak semua orang menganggapnya baik"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Penyetel Antarmuka Pengguna Sistem memberikan cara tambahan untuk mengubah dan menyesuaikan antarmuka pengguna Android. Fitur eksperimental ini dapat berubah, rusak, atau menghilang dalam rilis di masa mendatang. Lanjutkan dengan hati-hati."</string>
@@ -786,7 +788,7 @@
     <string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string>
     <string name="keyboard_key_backspace" msgid="4095278312039628074">"Backspace"</string>
     <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Play/Pause"</string>
-    <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Stop"</string>
+    <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Berhenti"</string>
     <string name="keyboard_key_media_next" msgid="8502476691227914952">"Next"</string>
     <string name="keyboard_key_media_previous" msgid="5637875709190955351">"Previous"</string>
     <string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Rewind"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 25c98ad..b3291bb 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Kveikt verður á Bluetooth í fyrramálið"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deila hljóði"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deilir hljóði"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"slá inn stillingar hljóðdeilingar"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Lokið"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Stillingar"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Kveikt"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Kveikt • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Slökkt"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Setja upp"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Stjórna í stillingum"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Gervihnöttur, góð tenging"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Gervihnöttur, tenging tiltæk"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Gervihnattar-SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Vinnusnið"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Þetta er ekki allra"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Fínstillingar kerfisviðmóts gera þér kleift að fínstilla og sérsníða notendaviðmót Android. Þessir tilraunaeiginleikar geta breyst, bilað eða horfið í síðari útgáfum. Gakktu því hægt um gleðinnar dyr."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index eb941db..b37eff9 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Il Bluetooth verrà attivato domani mattina"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Condividi audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Condivisione audio in corso…"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"inserisci le impostazioni di condivisione audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
@@ -385,14 +384,14 @@
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Avvia"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Interrompi"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Segnalazione di bug"</string>
-    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Quali problemi ha l\'esperienza del dispositivo?"</string>
+    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Quali problemi ha l\'esperienza con il dispositivo?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Seleziona il tipo di problema"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Registrazione schermo"</string>
     <string name="performance" msgid="6552785217174378320">"Prestazioni"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interfaccia utente"</string>
     <string name="thermal" msgid="6758074791325414831">"Termico"</string>
     <string name="custom" msgid="3337456985275158299">"Personalizzate"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Impostazioni monitoraggio personalizzate"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Impostazioni di traccia personalizzate"</string>
     <string name="restore_default" msgid="5259420807486239755">"Ripristina predefinite"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Modalità a una mano"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Apparecchi acustici"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Fine"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Impostazioni"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"On"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"On • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configura"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gestisci nelle impostazioni"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitare, connessione buona"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitare, connessione disponibile"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satellitare"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profilo di lavoro"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Il divertimento riservato a pochi eletti"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"L\'Ottimizzatore UI di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index be64a37..ecb8409 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -150,12 +150,12 @@
     <string name="cast_to_other_device_stop_dialog_message_generic" msgid="4100272100480415076">"‏מתבצעת כרגע פעולת Cast למכשיר בקרבת מקום"</string>
     <string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"‏הפסקת ה-Cast"</string>
     <string name="close_dialog_button" msgid="4749497706540104133">"סגירה"</string>
-    <string name="issuerecord_title" msgid="286627115110121849">"בעיה במכשיר ההקלטה"</string>
+    <string name="issuerecord_title" msgid="286627115110121849">"תיעוד של בעיה"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"מתבצע עיבוד של בעיית ההקלטה"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"התראה מתמשכת לסשן איסוף הבעיה"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"בעיית הקלטה"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"הבעיה בתהליך הקלטה"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"שיתוף"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"בעיית ההקלטה נשמרה"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"הקלטת הבעיה נשמרה"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"אפשר להקיש כדי להציג"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"שגיאה בשמירה של בעיית ההקלטה"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"שגיאה בהפעלה של בעיית ההקלטה"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"‏חיבור ה-Bluetooth יופעל מחר בבוקר"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"שיתוף האודיו"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"מתבצע שיתוף של האודיו"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"להזנת הרשאות השיתוף של האודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> סוללה"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"סיום"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"הגדרות"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"מצב מופעל"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"פועל • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"מצב מושבת"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"הגדרה"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"שינוי ב\'הגדרות\'"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"לוויין, חיבור באיכות טובה"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"לוויין, יש חיבור זמין"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"תקשורת לוויינית למצב חירום"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"פרופיל עבודה"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"מהנה בשביל חלק מהאנשים, אבל לא בשביל כולם"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏התכונה System UI Tuner מספקת לך דרכים נוספות להתאים אישית את ממשק המשתמש של Android. התכונות הניסיוניות האלה עשויות להשתנות, לא לעבוד כראוי או להיעלם בגרסאות עתידיות. יש להמשיך בזהירות."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 1ad16d4..98d320d 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"明日の朝に Bluetooth が ON になります"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"音声を共有"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"音声を共有中"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"音声の共有設定を開く"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"バッテリー <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"完了"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"設定"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ON"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ON • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"OFF"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"設定"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"設定で管理"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛生、接続状態良好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛生、接続利用可能"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"衛星 SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"仕事用プロファイル"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"一部の方のみお楽しみいただける限定公開ツール"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"システムUI調整ツールでは、Androidユーザーインターフェースの調整やカスタマイズを行えます。これらの試験運用機能は今後のリリースで変更となったり、中止となったり、削除されたりする可能性がありますのでご注意ください。"</string>
@@ -1315,7 +1317,7 @@
     <string name="keyguard_affordance_enablement_dialog_notes_app_action" msgid="6821710209675089470">"アプリを選択"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"ショートカットの長押しが必要です"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"キャンセル"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="1507591562761552899">"画面を切り替えましょう"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="1507591562761552899">"画面を切り替える"</string>
     <string name="rear_display_folded_bottom_sheet_title" msgid="3930008746560711990">"スマートフォンを開いてください"</string>
     <string name="rear_display_unfolded_bottom_sheet_title" msgid="6291111173057304055">"画面を切り替えますか?"</string>
     <string name="rear_display_folded_bottom_sheet_description" msgid="6842767125783222695">"高解像度で撮るには背面カメラを使用してください"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 6e11261..d85d7c1 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ჩაირთვება ხვალ დილით"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"აუდიოს გაზიარება"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"მიმდინარებოს აუდიოს გაზიარება"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"აუდიო გაზიარების პარამეტრების შეყვანა"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ბატარეა"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"მზადაა"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"პარამეტრები"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ჩართული"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ჩართულია • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"გამორთული"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"დაყენება"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"პარამეტრებში მართვა"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"კარგი სატელიტური კავშირი"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ხელმისაწვდომია სატელიტური კავშირი"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"სატელიტური SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"სამსახურის პროფილი"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ზოგისთვის გასართობია, მაგრამ არა ყველასთვის"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"სისტემის UI ტუნერი გაძლევთ დამატებით გზებს Android-ის სამომხმარებლო ინტერფეისის პარამეტრების დაყენებისთვის. ეს ექსპერიმენტული მახასიათებლები შეიძლება შეიცვალოს, შეწყდეს ან გაქრეს მომავალ ვერსიებში. სიფრთხილით გააგრძელეთ."</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d111ff1..7a43b4b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -150,12 +150,12 @@
     <string name="cast_to_other_device_stop_dialog_message_generic" msgid="4100272100480415076">"Қазір маңайдағы құрылғыға трансляциялап жатырсыз."</string>
     <string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"Трансляцияны тоқтату"</string>
     <string name="close_dialog_button" msgid="4749497706540104133">"Жабу"</string>
-    <string name="issuerecord_title" msgid="286627115110121849">"Мәселені жазу құралы"</string>
+    <string name="issuerecord_title" msgid="286627115110121849">"Ақау жазу құралы"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Мәселе жазбасы өңделіп жатыр"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Мәселе туралы дерек жинау сеансына арналған ағымдағы хабарландыру"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Мәселе жазылып жатыр"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Ақау жазылып жатыр"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Бөлісу"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"Мәселе жазбасы сақталды"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"Ақау жазбасы сақталды."</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Көру үшін түртіңіз."</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"Мәселе жазбасын сақтау кезінде қате шықты."</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Мәселені жазуды бастау кезінде қате шықты."</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ертең таңертең қосылады."</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Аудионы бөлісу"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Аудио беріліп жатыр"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"аудио бөлісу параметрлерін енгізу"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батарея деңгейі: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Дайын"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Параметрлер"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Қосулы"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Қосулы • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Өшірулі"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Реттеу"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"\"Параметрлер\" бөлімінде реттеу"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Жерсерік, байланыс жақсы."</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Жерсерік, байланыс бар."</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Жұмыс профилі"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Кейбіреулерге қызық, бірақ барлығына емес"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Жүйелік пайдаланушылық интерфейс тюнері Android пайдаланушылық интерфейсін реттеудің қосымша жолдарын береді. Бұл эксперименттік мүмкіндіктер болашақ шығарылымдарда өзгеруі, бұзылуы немесе жоғалуы мүмкін. Сақтықпен жалғастырыңыз."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index c686855..2319623 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ប៊្លូធូសនឹងបើកនៅព្រឹកស្អែក"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ស្ដាប់សំឡេងរួមគ្នា"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"កំពុងស្ដាប់សំឡេងរួមគ្នា"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"បញ្ចូលការកំណត់ការស្ដាប់សំឡេងរួមគ្នា"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"រួចរាល់"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ការកំណត់"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"បើក"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"បើក • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"បិទ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"រៀបចំ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"គ្រប់គ្រង​នៅ​ក្នុង​ការកំណត់"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ផ្កាយរណប មានការតភ្ជាប់ល្អ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ផ្កាយរណប អាចតភ្ជាប់បាន"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ការប្រកាសអាសន្នតាមផ្កាយរណប"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"កម្រងព័ត៌មានការងារ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ល្អសម្រាប់អ្នកប្រើមួយចំនួន តែមិនសម្រាប់គ្រប់គ្នាទេ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"កម្មវិធីសម្រួល UI ប្រព័ន្ធផ្តល់ជូនអ្នកនូវមធ្យោបាយបន្ថែមទៀតដើម្បីកែសម្រួល និងប្តូរចំណុចប្រទាក់អ្នកប្រើ Android តាមបំណង។ លក្ខណៈពិសេសសាកល្បងនេះអាចនឹងផ្លាស់ប្តូរ បំបែក ឬបាត់បង់បន្ទាប់ពីការចេញផ្សាយនាពេលអនាគត។ សូមបន្តដោយប្រុងប្រយ័ត្ន។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 9e22f64..aeb3b1ef0 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ಬ್ಲೂಟೂತ್ ನಾಳೆ ಬೆಳಗ್ಗೆ ಆನ್ ಆಗುತ್ತದೆ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳಿ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ಆಡಿಯೋವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಮೂದಿಸಿ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್‌ಸೆಟ್"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ಮುಗಿದಿದೆ"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ಆನ್ ಆಗಿದೆ"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"<xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g> • ನಲ್ಲಿ"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ಆಫ್ ಆಗಿದೆ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ಸೆಟಪ್ ಮಾಡಿ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನಿರ್ವಹಿಸಿ"</string>
@@ -465,7 +465,7 @@
     <string name="phone_hint" msgid="6682125338461375925">"ಫೋನ್‌ಗಾಗಿ ಐಕಾನ್‌ನಿಂದ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="voice_hint" msgid="7476017460191291417">"ಧ್ವನಿ ಸಹಾಯಕ್ಕಾಗಿ ಐಕಾನ್‌ನಿಂದ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="camera_hint" msgid="4519495795000658637">"ಕ್ಯಾಮರಾಗಾಗಿ ಐಕಾನ್‌ನಿಂದ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
-    <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"ಒಟ್ಟು ಮೌನ. ಇದು ಪರದೆ ರೀಡರ್ ಅನ್ನು ಮೌನವಾಗಿರಿಸುತ್ತದೆ."</string>
+    <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"ಒಟ್ಟು ಮೌನ. ಇದು ಸ್ಕ್ರೀನ್ ರೀಡರ್ ಅನ್ನು ಮೌನವಾಗಿರಿಸುತ್ತದೆ."</string>
     <string name="interruption_level_none" msgid="219484038314193379">"ಸಂಪೂರ್ಣ ನಿಶ್ಯಬ್ಧ"</string>
     <string name="interruption_level_priority" msgid="661294280016622209">"ಆದ್ಯತೆ ಮಾತ್ರ"</string>
     <string name="interruption_level_alarms" msgid="2457850481335846959">"ಅಲಾರಮ್‌ಗಳು ಮಾತ್ರ"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ಸ್ಯಾಟಲೈಟ್‌, ಕನೆಕ್ಷನ್ ಉತ್ತಮವಾಗಿದೆ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ಸ್ಯಾಟಲೈಟ್, ಕನೆಕ್ಷನ್ ಲಭ್ಯವಿದೆ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ಸ್ಯಾಟಲೈಟ್ SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ಕೆಲವರಿಗೆ ಮೋಜು ಆಗಿದೆ ಎಲ್ಲರಿಗೆ ಇಲ್ಲ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ಸಿಸ್ಟಂ UI ಟ್ಯೂನರ್ ನಿಮಗೆ Android ಬಳಕೆದಾರ ಅಂತರಸಂಪರ್ಕವನ್ನು ಸರಿಪಡಿಸಲು ಮತ್ತು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ಹೆಚ್ಚುವರಿ ಮಾರ್ಗಗಳನ್ನು ನೀಡುತ್ತದೆ. ಈ ಪ್ರಾಯೋಗಿಕ ವೈಶಿಷ್ಟ್ಯಗಳು ಭವಿಷ್ಯದ ಬಿಡುಗಡೆಗಳಲ್ಲಿ ಬದಲಾಗಬಹುದು, ವಿರಾಮವಾಗಬಹುದು ಅಥವಾ ಕಾಣಿಸಿಕೊಳ್ಳದಿರಬಹುದು. ಎಚ್ಚರಿಕೆಯಿಂದ ಮುಂದುವರಿಯಿರಿ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 70a994f..277582c 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"블루투스가 내일 아침에 켜집니다."</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"오디오 공유"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"오디오 공유 중"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"오디오 공유 설정으로 이동"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"배터리 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"완료"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"설정"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"사용"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"켜짐 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"사용 안함"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"설정"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"설정에서 관리"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"위성, 연결 상태 양호"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"위성, 연결 가능"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"위성 긴급 SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"직장 프로필"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"마음에 들지 않을 수도 있음"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"시스템 UI 튜너를 사용하면 Android 사용자 인터페이스를 변경 및 맞춤설정할 수 있습니다. 이러한 실험실 기능은 향후 출시 버전에서는 변경되거나 다운되거나 사라질 수 있습니다. 신중하게 진행하시기 바랍니다."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 0643033..4680236 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -153,9 +153,9 @@
     <string name="issuerecord_title" msgid="286627115110121849">"Маселе жаздыргыч"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Маселе жаздырылууда"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Маселе тууралуу маалымат чогултулуп жатканы жөнүндө учурдагы билдирме"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Жаздыруу маселеси"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Маселе жазылууда"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Бөлүшүү"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"Жаздырылган маселе сакталды"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"Маселе жаздырылды"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Көрүү үчүн таптаңыз"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"Жаздырылган маселе сакталган жок"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Башталбай койду"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth эртең таңда күйөт"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Чогуу угуу"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Чогуу угулууда"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"чогуу угуу параметрлерин киргизүү"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батареянын деңгээли <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Бүттү"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Параметрлер"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Күйүк"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Күйүк • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Өчүк"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Тууралоо"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Параметрлерден тескөө"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутник, байланыш жакшы"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спутник, байланыш бар"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутник SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Жумуш профили"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Баарына эле жага бербейт"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android колдонуучу интерфейсин жөнгө салып жана ыңгайлаштыруунун кошумча ыкмаларын сунуштайт. Бул сынамык функциялар кийинки чыгарылыштарда өзгөрүлүп, бузулуп же жоголуп кетиши мүмкүн. Абайлап колдонуңуз."</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index c9c136b..edb8fa3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ຈະເປີດມື້ອື່ນເຊົ້າ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ແບ່ງປັນສຽງ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ກຳລັງແບ່ງປັນສຽງ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ເຂົ້າສູ່ການຕັ້ງຄ່າການແບ່ງປັນສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ແບັດເຕີຣີ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ແລ້ວໆ"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ການຕັ້ງຄ່າ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ເປີດ"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ເປີດ • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ປິດ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ຕັ້ງຄ່າ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ຈັດການໃນການຕັ້ງຄ່າ"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ດາວທຽມ, ການເຊື່ອມຕໍ່ດີ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ດາວທຽມ, ການເຊື່ອມຕໍ່ທີ່ພ້ອມນຳໃຊ້"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ດາວທຽມ"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"​ໂປຣ​ໄຟລ໌​ບ່ອນ​ເຮັດ​ວຽກ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ມ່ວນຊື່ນສຳລັບບາງຄົນ ແຕ່ບໍ່ແມ່ນສຳລັບທຸກຄົນ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner ໃຫ້ທ່ານມີວິທີພິເສດຕື່ມອີກໃນການປັບປ່ຽນ ແລະຕົບແຕ່ງສ່ວນຕໍ່ປະສານຜູ້ໃຊ້ຂອງ Android. ຄຸນສົມບັດທົດລອງໃຊ້ເຫຼົ່ານີ້ອາດຈະປ່ຽນແປງ, ຢຸດເຊົາ ຫຼືຫາຍໄປໃນການວາງຈຳໜ່າຍໃນອະນາຄົດ. ຈົ່ງດຳເນີນຕໍ່ດ້ວຍຄວາມລະມັດລະວັງ."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index fdffbfc..a9a0e73 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"„Bluetooth“ ryšys bus įjungtas rytoj ryte"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Bendrinti garsą"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Bendrinamas garsas"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"įvesti garso įrašų bendrinimo nustatymus"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Atlikta"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Nustatymai"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Įjungta"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Įjungta • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Išjungta"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Nustatyti"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Tvarkyti skiltyje „Nustatymai“"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Palydovas, geras ryšys"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Palydovas, pasiekiamas ryšys"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Prisijungimas prie palydovo kritiniu atveju"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darbo profilis"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Smagu, bet ne visada"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistemos naudotojo sąsajos derinimo priemonė suteikia papildomų galimybių pagerinti ir tinkinti „Android“ naudotojo sąsają. Šios eksperimentinės funkcijos gali pasikeisti, nutrūkti ar išnykti iš būsimų laidų. Tęskite atsargiai."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index dfef9cc..d1e97a6 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth savienojums tiks ieslēgts rīt no rīta"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Kopīgot audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Notiek audio kopīgošana…"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"atvērt audio kopīgošanas iestatījumus"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumulators: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gatavs"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Iestatījumi"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ieslēgts"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ieslēgts • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Izslēgts"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Iestatīt"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Pārvaldīt iestatījumos"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelīts, labs savienojums"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelīts, ir pieejams savienojums"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelīta SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darba profils"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Jautri dažiem, bet ne visiem"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistēmas saskarnes regulators sniedz papildu veidus, kā mainīt un pielāgot Android lietotāja saskarni. Nākamajās versijās šīs eksperimentālās funkcijas var tikt mainītas, bojātas vai to darbība var tikt pārtraukta. Turpinot esiet uzmanīgs."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e90ec8fe..7691ae6 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ќе се вклучи утре наутро"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Споделувај аудио"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Се споделува аудио"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"внесете ги поставките за „Споделување аудио“"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерија: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Готово"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Поставки"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Вклучено"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Вклучено: <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Исклучено"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Поставете"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Управувајте во поставките"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Добра сателитска врска"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Достапна е сателитска врска"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Сателитски SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Работен профил"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забава за некои, но не за сите"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Адаптерот на УИ на системот ви дава дополнителни начини за дотерување и приспособување на корисничкиот интерфејс на Android. Овие експериментални функции можеби ќе се изменат, расипат или ќе исчезнат во следните изданија. Продолжете со претпазливост."</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index bb8a91e..8c0378a 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth നാളെ രാവിലെ ഓണാകും"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ഓഡിയോ പങ്കിടുക"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ഓഡിയോ പങ്കിടുന്നു"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ഓഡിയോ പങ്കിടൽ ക്രമീകരണം നൽകുക"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ബാറ്ററി"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്‌സെറ്റ്"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ശരി"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ക്രമീകരണം"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ഓണാണ്"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ഓണാണ് • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ഓഫാണ്"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"സജ്ജീകരിക്കുക"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ക്രമീകരണത്തിൽ മാനേജ് ചെയ്യുക"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"സാറ്റലൈറ്റ്, മികച്ച കണക്ഷൻ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"സാറ്റലൈറ്റ്, കണക്ഷൻ ലഭ്യമാണ്"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"സാറ്റലൈറ്റ് SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ചിലർക്ക് വിനോദം, എന്നാൽ എല്ലാവർക്കുമില്ല"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Android ഉപയോക്തൃ ഇന്റർഫേസ് ആവശ്യമുള്ള രീതിയിൽ മാറ്റുന്നതിനും ഇഷ്ടാനുസൃതമാക്കുന്നതിനും സിസ്റ്റം UI ട്യൂണർ നിങ്ങൾക്ക് അധിക വഴികൾ നൽകുന്നു. ഭാവി റിലീസുകളിൽ ഈ പരീക്ഷണാത്മക ഫീച്ചറുകൾ മാറ്റുകയോ നിർത്തുകയോ അപ്രത്യക്ഷമാവുകയോ ചെയ്തേക്കാം. ശ്രദ്ധയോടെ മുന്നോട്ടുപോകുക."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 526e39b..bfd48a4 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-г маргааш өглөө асаана"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Аудио хуваалцах"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Аудио хуваалцаж байна"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"аудио хуваалцах тохиргоог оруулах"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Болсон"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Тохиргоо"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Асаалттай"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Асаасан • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Унтраалттай"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Тохируулах"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Тохиргоонд удирдах"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хиймэл дагуул, холболт сайн байна"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Хиймэл дагуул, холболт боломжтой"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хиймэл дагуул SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ажлын профайл"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Зарим хүнд хөгжилтэй байж болох ч бүх хүнд тийм биш"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Системийн UI Tохируулагч нь Android хэрэглэгчийн интерфэйсийг тааруулах, өөрчлөх нэмэлт аргыг зааж өгөх болно. Эдгээр туршилтын тохиргоо нь цаашид өөрчлөгдөх, эвдрэх, алга болох магадлалтай. Үйлдлийг болгоомжтой хийнэ үү."</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 8cf4855..d124ce3 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ब्लूटूथ उद्या सकाळी सुरू होईल"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ऑडिओ शेअर करा"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ऑडिओ शेअर करत आहे"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ऑडिओ शेअरिंग सेटिंग्ज एंटर करा"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -392,7 +391,7 @@
     <string name="user_interface" msgid="3712869377953950887">"यूझर इंटरफेस"</string>
     <string name="thermal" msgid="6758074791325414831">"थर्मल"</string>
     <string name="custom" msgid="3337456985275158299">"कस्टम"</string>
-    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"मागाच्या कस्टम सेटिंग्ज"</string>
+    <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"ट्रेससाठी कस्टम सेटिंग्ज"</string>
     <string name="restore_default" msgid="5259420807486239755">"डीफॉल्ट रिस्टोअर करा"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"एकहाती मोड"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"श्रवणयंत्रे"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"पूर्ण झाले"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"सेटिंग्ज"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"सुरू आहे"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"सुरू • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"बंद आहे"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"सेट करा"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"सेटिंग्जमध्ये व्यवस्थापित करा"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सॅटेलाइट, चांगले कनेक्शन"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सॅटेलाइट, कनेक्शन उपलब्ध"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सॅटेलाइट SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाईल"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"सर्वांसाठी नाही तर काहींसाठी मजेदार असू शकते"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनर आपल्‍याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्‍याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्‍ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्‍यातील रिलीज मध्‍ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 9b6781a..8385682 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dihidupkan esok pagi"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Kongsi audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Perkongsian audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"masukkan tetapan perkongsian audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Rakam skrin"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mula"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Rekodkan Masalah"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Rakam Masalah"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Mula"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Hentikan"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Laporan Pepijat"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Selesai"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Tetapan"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Hidup"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Pada • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Mati"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Sediakan"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Urus dalam tetapan"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, sambungan yang baik"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, sambungan tersedia"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Menarik untuk sesetengah orang tetapi bukan untuk semua"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Penala UI Sistem memberi anda cara tambahan untuk mengolah dan menyesuaikan antara muka Android. Ciri eksperimen ini boleh berubah, rosak atau hilang dalam keluaran masa hadapan. Teruskan dengan berhati-hati."</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index e9f07b9..60c74c1 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"မနက်ဖြန်နံနက်တွင် ဘလူးတုသ် ပွင့်ပါမည်"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"အသံမျှဝေရန်"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"အသံမျှဝေနေသည်"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"အော်ဒီယို မျှဝေခြင်း ဆက်တင်များ ထည့်ရန်"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ပြီးပြီ"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ဆက်တင်များ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ဖွင့်"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ဖွင့် • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ပိတ်"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"စနစ်ထည့်သွင်းရန်"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ဆက်တင်များတွင် စီမံရန်"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ကောင်းသည်"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ရနိုင်သည်"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"အလုပ် ပရိုဖိုင်"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"အချို့သူများ အတွက် ပျော်စရာ ဖြစ်ပေမဲ့ အားလုံး အတွက် မဟုတ်ပါ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"စနစ် UI Tuner က သင့်အတွက် Android အသုံးပြုသူ အင်တာဖေ့စ်ကို ပြောင်းရန်နှင့် စိတ်ကြိုက်ပြုလုပ်ရန် နည်းလမ်း အပိုများကို သင့်အတွက် စီစဉ်ပေးသည်။ အနာဂတ်ဗားရှင်းများတွင် ဤစမ်းသပ်အင်္ဂါရပ်များမှာ ပြောင်းလဲ၊ ပျက်စီး သို့မဟုတ် ပျောက်ကွယ်သွားနိုင်သည်။ သတိဖြင့် ရှေ့ဆက်ပါ။"</string>
@@ -1391,7 +1393,7 @@
     <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ပြန်သွားရန်"</string>
     <string name="touchpad_back_gesture_guidance" msgid="6263750214998421587">"နောက်ပြန်သွားရန် တာ့ချ်ပက်ပေါ်ရှိ မည်သည့်နေရာ၌မဆို လက်သုံးချောင်းဖြင့် ဘယ် (သို့) ညာသို့ ပွတ်ဆွဲပါ။\n\n၎င်းအတွက် လက်ကွက်ဖြတ်လမ်း Action + ESC ကိုလည်း သုံးနိုင်သည်။"</string>
     <string name="touchpad_back_gesture_success_title" msgid="7240576648330612171">"တော်ပါပေသည်။"</string>
-    <string name="touchpad_back_gesture_success_body" msgid="2324724953720741719">"နောက်ဆုတ်လက်ဟန် အပြီးသတ်လိုက်ပါပြီ။"</string>
+    <string name="touchpad_back_gesture_success_body" msgid="2324724953720741719">"နောက်သို့လက်ဟန် အပြီးသတ်လိုက်ပါပြီ"</string>
     <string name="touchpad_home_gesture_action_title" msgid="8885107349719257882">"ပင်မစာမျက်နှာသို့ သွားရန်"</string>
     <string name="touchpad_home_gesture_guidance" msgid="3043931356096731966">"ပင်မစာမျက်နှာသို့ အချိန်မရွေးသွားရန် စခရင်အောက်ခြေမှ အပေါ်သို့ လက်သုံးချောင်းဖြင့် ပွတ်ဆွဲပါ။"</string>
     <string name="touchpad_home_gesture_success_title" msgid="3778407003948209795">"ကောင်းသည်။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index bc172de..72ccd2c 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth slås på i morgen tidlig"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Del lyd"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Deler lyd"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"åpne innstillingene for lyddeling"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Ferdig"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Innstillinger"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"På"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"På • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Av"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Konfigurer"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Administrer i innstillingene"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitt – god tilkobling"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitt – tilkobling tilgjengelig"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-alarm via satellitt"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work-profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Gøy for noen – ikke for alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Med System UI Tuner har du flere måter å justere og tilpasse Android-brukergrensesnittet på. Disse eksperimentelle funksjonene kan endres, avbrytes eller fjernes i fremtidige utgivelser. Fortsett med forbehold."</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 49bf32f..d680645 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ब्लुटुथ भोलि बिहान अन हुने छ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"अडियो सेयर गर्नुहोस्"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"अडियो सेयर गरिँदै छ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"अडियो सेयर गर्ने सुविधासम्बन्धी सेटिङ हाल्न"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ब्याट्री"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"सम्पन्न भयो"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"सेटिङ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"अन छ"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"अन छ • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"अफ छ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"सेटअप गर्नुहोस्"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"सेटिङमा गई व्यवस्थापन गर्नुहोस्"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"स्याटलाइट, राम्रो कनेक्सन"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"स्याटलाइट, कनेक्सन उपलब्ध छ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"स्याटलाइट SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाइल"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"केहीका लागि रमाइलो हुन्छ तर सबैका लागि होइन"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनरले तपाईँलाई Android प्रयोगकर्ता इन्टरफेस  कस्टम गर्न र ट्विक गर्न थप तरिकाहरू प्रदान गर्छ। यी प्रयोगात्मक सुविधाहरू भावी विमोचनमा परिवर्तन हुन, बिग्रिन वा हराउन सक्ने छन्। सावधानीपूर्वक अगाडि बढ्नुहोस्।"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 36f88a0..6a675b8 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth wordt morgenochtend aangezet"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Audio delen"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audio delen"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"instellingen voor audio delen openen"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterijniveau"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Schermopname"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppen"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Probleem vastleggen"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Probleem opnemen"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Starten"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Stoppen"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Bugrapport"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Klaar"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Instellingen"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aan"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Aan • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Uit"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Instellen"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Beheren via instellingen"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goede verbinding"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding beschikbaar"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satelliet"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Leuk voor sommige gebruikers, maar niet voor iedereen"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Met Systeem-UI-tuner beschikt u over extra manieren om de Android-gebruikersinterface aan te passen. Deze experimentele functies kunnen veranderen, vastlopen of verdwijnen in toekomstige releases. Ga voorzichtig verder."</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 44eeb43..6e63643 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -156,7 +156,7 @@
     <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"ରେକର୍ଡିଂରେ ସମସ୍ୟା"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"ସେୟାର କରନ୍ତୁ"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"ସମସ୍ୟାର ରେକର୍ଡିଂକୁ ସେଭ କରାଯାଇଛି"</string>
-    <string name="issuerecord_save_text" msgid="1205985304551521495">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="issuerecord_save_text" msgid="1205985304551521495">"ଭ୍ୟୁ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"ସମସ୍ୟାର ରେକର୍ଡିଂ କରିବାରେ ତ୍ରୁଟି"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"ସମସ୍ୟାର ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
     <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ବ୍ଲୁଟୁଥ ଆସନ୍ତା କାଲି ସକାଳେ ଚାଲୁ ହେବ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ଅଡିଓ ସେୟାର କରନ୍ତୁ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ଅଡିଓ ସେୟାର କରାଯାଉଛି"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ଅଡିଓ ସେୟାରିଂ ସେଟିଂସରେ ପ୍ରବେଶ କରନ୍ତୁ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ବ୍ୟାଟେରୀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍‍"</string>
@@ -384,7 +383,7 @@
     <string name="qs_record_issue_label" msgid="8166290137285529059">"ସମସ୍ୟାର ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"ବଗ୍‌ ରିପୋର୍ଟ୍‌"</string>
+    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"ବଗ ରିପୋର୍ଟ"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ଆପଣଙ୍କ ଡିଭାଇସ ଅନୁଭୂତିର କେଉଁ ଅଂଶ ପ୍ରଭାବିତ ହୋଇଛି?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"ସମସ୍ୟାର ପ୍ରକାର ଚୟନ କରନ୍ତୁ"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ସ୍କ୍ରିନ ରେକର୍ଡ"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ହୋଇଗଲା"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ସେଟିଂସ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ଚାଲୁ ଅଛି"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ଚାଲୁ ଅଛି • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ବନ୍ଦ ଅଛି"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ସେଟ ଅପ କରନ୍ତୁ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ସେଟିଂସରେ ପରିଚାଳନା କରନ୍ତୁ"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ସାଟେଲାଇଟ, ଭଲ କନେକ୍ସନ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ସାଟେଲାଇଟ, କନେକ୍ସନ ଉପଲବ୍ଧ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ସେଟେଲାଇଟ SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"କେତେକଙ୍କ ପାଇଁ ମଜାଦାର, କିନ୍ତୁ ସମସ୍ତଙ୍କ ପାଇଁ ନୁହେଁ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Android ୟୁଜର୍‍ ଇଣ୍ଟରଫେସ୍‍ ବଦଳାଇବାକୁ ତଥା ନିଜ ପସନ୍ଦ ଅନୁଯାୟୀ କରିବାକୁ ସିଷ୍ଟମ୍‍ UI ଟ୍ୟୁନର୍‍ ଆପଣଙ୍କୁ ଅତିରିକ୍ତ ଉପାୟ ପ୍ରଦାନ କରେ। ଏହି ପରୀକ୍ଷାମୂଳକ ସୁବିଧାମାନ ବଦଳିପାରେ, ଭାଙ୍ଗିପାରେ କିମ୍ବା ଭବିଷ୍ୟତର ରିଲିଜ୍‌ଗୁଡ଼ିକରେ ନଦେଖାଯାଇପାରେ। ସତର୍କତାର ସହ ଆଗକୁ ବଢ଼ନ୍ତୁ।"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index caa98ea..cd7790f 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ਬਲੂਟੁੱਥ ਕੱਲ੍ਹ ਸਵੇਰੇ ਚਾਲੂ ਹੋ ਜਾਵੇਗਾ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ਆਡੀਓ ਨੂੰ ਸਾਂਝਾ ਕਰੋ"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ਆਡੀਓ ਨੂੰ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਸੈਟਿੰਗਾਂ ਦਾਖਲ ਕਰੋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ਹੋ ਗਿਆ"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ਚਾਲੂ"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"<xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g> • \'ਤੇ"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ਬੰਦ"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ਸੈੱਟਅੱਪ ਕਰੋ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਵਧੀਆ ਹੈ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਉਪਲਬਧ ਹੈ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ਸੈਟੇਲਾਈਟ SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ਕੁਝ ਵਾਸਤੇ ਤਾਂ ਮਜ਼ੇਦਾਰ ਹੈ ਲੇਕਿਨ ਸਾਰਿਆਂ ਵਾਸਤੇ ਨਹੀਂ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ਸਿਸਟਮ UI ਟਿਊਨਰ ਤੁਹਾਨੂੰ Android ਵਰਤੋਂਕਾਰ ਇੰਟਰਫ਼ੇਸ ਤਬਦੀਲ ਕਰਨ ਅਤੇ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਵਾਧੂ ਤਰੀਕੇ ਦਿੰਦਾ ਹੈ। ਇਹ ਪ੍ਰਯੋਗਾਤਮਿਕ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਭਵਿੱਖ ਦੀ ਰੀਲੀਜ਼ ਵਿੱਚ ਬਦਲ ਸਕਦੀਆਂ ਹਨ, ਟੁੱਟ ਸਕਦੀਆਂ ਹਨ, ਜਾਂ ਅਲੋਪ ਹੋ ਸਕਦੀਆਂ ਹਨ। ਸਾਵਧਾਨੀ ਨਾਲ ਅੱਗੇ ਵੱਧੋ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 9a0560c..cf88172 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Nagrywać ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nagrywaj jedną aplikację"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nagrywaj cały ekran"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kiedy nagrywasz cały ekran, wszystko, co jest na nim widoczne, zostaje nagrane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kiedy nagrywasz cały ekran, nagrane zostanie wszystko, co jest na nim widoczne. Dlatego uważaj na hasła, dane do płatności, wiadomości, zdjęcia, nagrania audio czy filmy."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kiedy nagrywasz aplikację, wszystko, co jest w niej wyświetlane lub odtwarzane, zostaje nagrane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nagrywaj ekran"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Wybieranie aplikacji do nagrywania"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth włączy się jutro rano"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Udostępnij dźwięk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Udostępnia dźwięk"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"aby otworzyć ustawienia udostępniania dźwięku"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string>
@@ -378,20 +377,20 @@
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"Komunikacja NFC"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Komunikacja NFC jest wyłączona"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Komunikacja NFC jest włączona"</string>
-    <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Nagrywanie ekranu"</string>
+    <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Nagraj ekran"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Rozpocznij"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zatrzymaj"</string>
     <string name="qs_record_issue_label" msgid="8166290137285529059">"Zarejestruj problem"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Rozpocznij"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Zatrzymaj"</string>
-    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Raport o błędzie"</string>
+    <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Zgłoś błąd"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Którego aspektu korzystania z urządzenia dotyczył problem?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Wybierz typ problemu"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Nagrywanie ekranu"</string>
     <string name="performance" msgid="6552785217174378320">"Wydajność"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interfejs"</string>
-    <string name="thermal" msgid="6758074791325414831">"Termografia"</string>
-    <string name="custom" msgid="3337456985275158299">"Niestandardowe"</string>
+    <string name="thermal" msgid="6758074791325414831">"Temperatura"</string>
+    <string name="custom" msgid="3337456985275158299">"Niestandardowy"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Niestandardowe ustawienia śladu"</string>
     <string name="restore_default" msgid="5259420807486239755">"Przywróć wartości domyślne"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Tryb jednej ręki"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gotowe"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ustawienia"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Wł."</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Włączone • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Wył."</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Skonfiguruj"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Zarządzaj w ustawieniach"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelita – połączenie dobre"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelita – połączenie dostępne"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelitarne połączenie alarmowe"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil służbowy"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Dobra zabawa, ale nie dla każdego"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Kalibrator System UI udostępnia dodatkowe sposoby dostrajania i dostosowywania interfejsu Androida. Te eksperymentalne funkcje mogą się zmienić, popsuć lub zniknąć w przyszłych wersjach. Zachowaj ostrożność."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index b6bb114..c2c40db 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"O Bluetooth será ativado amanhã de manhã"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartilhar áudio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartilhando áudio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"acessar configurações de compartilhamento de áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -390,7 +389,7 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravação de tela"</string>
     <string name="performance" msgid="6552785217174378320">"Desempenho"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interface do usuário"</string>
-    <string name="thermal" msgid="6758074791325414831">"Térmico"</string>
+    <string name="thermal" msgid="6758074791325414831">"Temperatura"</string>
     <string name="custom" msgid="3337456985275158299">"Personalizado"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Configurações de rastreamento personalizado"</string>
     <string name="restore_default" msgid="5259420807486239755">"Restaurar padrão"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Concluído"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configurações"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ativado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ativado • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desativado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gerenciar nas configurações"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 68a977e..c52354f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"O Bluetooth vai ser ativado amanhã de manhã"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Partilhar áudio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"A partilhar áudio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"aceder às definições de partilha de áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string>
@@ -385,7 +384,7 @@
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Parar"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Relatório de erro"</string>
-    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Que parte do dispositivo foi afetada?"</string>
+    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Que experiência com o dispositivo foi afetada?"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecione o tipo de problema"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravação de ecrã"</string>
     <string name="performance" msgid="6552785217174378320">"Desempenho"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Concluir"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Definições"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ativado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ativado • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desativado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gerir nas definições"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa ligação"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, ligação disponível"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satélite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O Sintonizador da interface do sistema disponibiliza-lhe formas adicionais ajustar e personalizar a interface do utilizador do Android. Estas funcionalidades experimentais podem ser alteradas, deixar de funcionar ou desaparecer em versões futuras. Prossiga com cuidado."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index b6bb114..c2c40db 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"O Bluetooth será ativado amanhã de manhã"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Compartilhar áudio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Compartilhando áudio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"acessar configurações de compartilhamento de áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -390,7 +389,7 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravação de tela"</string>
     <string name="performance" msgid="6552785217174378320">"Desempenho"</string>
     <string name="user_interface" msgid="3712869377953950887">"Interface do usuário"</string>
-    <string name="thermal" msgid="6758074791325414831">"Térmico"</string>
+    <string name="thermal" msgid="6758074791325414831">"Temperatura"</string>
     <string name="custom" msgid="3337456985275158299">"Personalizado"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Configurações de rastreamento personalizado"</string>
     <string name="restore_default" msgid="5259420807486239755">"Restaurar padrão"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Concluído"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configurações"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ativado"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ativado • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Desativado"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurar"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gerenciar nas configurações"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 684b04c..1f7b7164 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se va activa mâine dimineață"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Trimite audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Se permite accesul la conținutul audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"accesa setările de permitere a accesului la audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Gata"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Setări"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Activat"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activat • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Dezactivat"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Configurează"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Gestionează în setări"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, conexiune bună"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, conexiune disponibilă"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prin satelit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil de serviciu"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Distractiv pentru unii, dar nu pentru toată lumea"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 7045163..0b545ce 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth включится завтра утром"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Отправить аудио"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Отправка аудио"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"перейти в настройки передачи аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Готово"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Настройки"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Включено"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Вкл. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Отключено"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Настроить"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Открыть настройки"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутниковая связь, хорошее качество соединения"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступно соединение по спутниковой связи"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутниковый SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Рабочий профиль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Внимание!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner позволяет настраивать интерфейс устройства Android по вашему вкусу. В будущем эта экспериментальная функция может измениться, перестать работать или исчезнуть."</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 72924e9..b34b8a3 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"බ්ලූටූත් හෙට උදේ සක්‍රීය වෙයි"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ශ්‍රව්‍ය බෙදා ගන්න"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ශ්‍රව්‍ය බෙදා ගැනීම"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ශ්‍රව්‍ය බෙදා ගැනීමේ සැකසීම් ඇතුළු කරන්න"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්‍රව්‍ය"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"නිමයි"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"සැකසීම්"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ක්‍රියාත්මකයි"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ක්‍රියාත්මකයි • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ක්‍රියාවිරහිතයි"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"පිහිටුවන්න"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"සැකසීම් තුළ කළමනාකරණය කරන්න"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"චන්ද්‍රිකාව, හොඳ සම්බන්ධතාවයක්"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"චන්ද්‍රිකාව, සම්බන්ධතාවය තිබේ"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"චන්ද්‍රිකා SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"කාර්යාල පැතිකඩ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"සමහරක් දේවල් වලට විනෝදයි, නමුත් සියල්ලටම නොවේ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"පද්ධති UI සුසරකය ඔබට Android පරිශීලක අතුරු මුහුණත වෙනස් කිරීමට හෝ අභිරුචිකරණය කිරීමට අමතර ක්‍රම ලබා දේ. මෙම පර්යේෂණාත්මක අංග ඉදිරි නිකුත් වීම් වල වෙනස් වීමට, වැඩ නොකිරීමට, හෝ නැතිවීමට හැක. ප්‍රවේශමෙන් ඉදිරියට යන්න."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 01399bd..b1983b8 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth sa zapne zajtra ráno"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Zdieľať zvuk"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Zdieľa sa zvuk"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"prejsť do nastavení zdieľania zvuku"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Rekordér obrazovky"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začať"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončiť"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Nahrať problém"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Zaznamenať problém"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Začať"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Zastavte"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Hlásenie chyby"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Hotovo"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Nastavenia"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Zapnuté"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Zapnuté • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Vypnuté"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Nastavenie"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Správa v nastaveniach"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobrá kvalita pripojenia"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, pripojenie je k dispozícii"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Pomoc cez satelit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovný profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Pri používaní tuneru postupujte opatrne"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Tuner používateľského rozhrania systému poskytujte ďalšie spôsoby ladenia a prispôsobenia používateľského rozhrania Android. Tieto experimentálne funkcie sa môžu v budúcich verziách zmeniť, ich poskytovanie môže byť prerušené alebo môžu byť odstránené. Pokračujte opatrne."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ad81798..a3b042e 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se bo vklopil jutri zjutraj"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Deli zvok"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Poteka deljenje zvoka"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"odpiranje nastavitev deljenja zvoka"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string>
@@ -390,7 +389,7 @@
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Snemanje zaslona"</string>
     <string name="performance" msgid="6552785217174378320">"Učinkovitost delovanja"</string>
     <string name="user_interface" msgid="3712869377953950887">"Uporabniški vmesnik"</string>
-    <string name="thermal" msgid="6758074791325414831">"Toplotno"</string>
+    <string name="thermal" msgid="6758074791325414831">"Toplota"</string>
     <string name="custom" msgid="3337456985275158299">"Po meri"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Nastavitve sledi po meri"</string>
     <string name="restore_default" msgid="5259420807486239755">"Obnovi privzeto"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Končano"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Nastavitve"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Vklopljeno"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Vklopljeno • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Izklopljeno"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Nastavitev"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Upravljanje v nastavitvah"</string>
@@ -673,7 +673,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Prostorski zvok"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Izklopljeno"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fiksno"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Spremljanje položaja glave"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Spremljanje premikov glave"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Dotaknite se, če želite spremeniti način zvonjenja."</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"izklop zvoka"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"vklop zvoka"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra povezava"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, povezava je na voljo"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prek satelita"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Delovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabavno za nekatere, a ne za vse"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Uglaševalnik uporabniškega vmesnika sistema vam omogoča dodatne načine za spreminjanje in prilagajanje uporabniškega vmesnika Android. Te poskusne funkcije lahko v prihodnjih izdajah kadar koli izginejo, se spremenijo ali pokvarijo. Bodite previdni."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 3ddbea5..4cba527 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-i do të aktivizohet nesër në mëngjes"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Ndaj audion"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audioja po ndahet"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"për të hyrë te cilësimet e ndarjes së audios"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"U krye"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Cilësimet"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Aktiv"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Aktiv • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Joaktiv"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Konfiguro"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Menaxho te cilësimet"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sateliti. Lidhje e mirë"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sateliti. Ofrohet lidhje"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satelitor"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profili i punës"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Argëtim për disa, por jo për të gjithë!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit të jep mënyra shtesë për të tërhequr dhe personalizuar ndërfaqen Android të përdoruesit. Këto funksione eksperimentale mund të ndryshojnë, prishen ose zhduken në versionet e ardhshme. Vazhdo me kujdes."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 9a3196c..26c135b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ће се укључити сутра ујутру"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Дели звук"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Дели се звук"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"уђите у подешавања дељења звука"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Готово"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Подешавања"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Укључено"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Укљ. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Искључено"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Подеси"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Управљајте у подешавањима"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, веза је добра"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, веза је доступна"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хитна помоћ преко сателита"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Пословни профил"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забава за неке, али не за све"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Тјунер за кориснички интерфејс система вам пружа додатне начине за подешавање и прилагођавање Android корисничког интерфејса. Ове експерименталне функције могу да се промене, откажу или нестану у будућим издањима. Будите опрезни."</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index cf9a2a2..56cc442 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth aktiveras i morgon bitti"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Dela ljud"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Delar ljud"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"öppna inställningarna för ljuddelning"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Skärminspelning"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starta"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppa"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Registrera problem"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Anmäl problem"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Starta"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Stoppa"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Felrapport"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Klar"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Inställningar"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"På"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"På • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Av"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Ställ in"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Hantera i inställningarna"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, bra anslutning"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, anslutning tillgänglig"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-larm via satellit"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Jobbprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kul för vissa, inte för alla"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Du kan använda inställningarna för systemgränssnitt för att justera användargränssnittet i Android. Dessa experimentfunktioner kan när som helst ändras, sluta fungera eller försvinna. Använd med försiktighet."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 317d7f1..db237b5 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth itawaka kesho asubuhi"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Sikiliza pamoja na wengine"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Mnasikiliza pamoja"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"uweke mipangilio ya kusikiliza pamoja"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Nimemaliza"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Mipangilio"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Imewashwa"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Imewashwa • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Imezimwa"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Weka mipangilio"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Dhibiti katika mipangilio"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Setilaiti, muunganisho thabiti"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Setilaiti, muunganisho unapatikana"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Msaada kupitia Setilaiti"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Wasifu wa kazini"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kinafurahisha kwa baadhi ya watu lakini si wote"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Kirekebishi cha kiolesura cha mfumo kinakupa njia zaidi za kugeuza na kubadilisha kiolesura cha Android ili kikufae. Vipengele hivi vya majaribio vinaweza kubadilika, kuharibika au kupotea katika matoleo ya siku zijazo. Endelea kwa uangalifu."</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index 0c11d2f..fc6d20e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -27,8 +27,6 @@
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">true</bool>
 
-    <bool name="config_use_large_screen_shade_header">true</bool>
-
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">2</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index c594f1c..b438315 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,6 +35,8 @@
     <!-- How many lines to show in the security footer -->
     <integer name="qs_security_footer_maxLines">1</integer>
 
+    <bool name="config_use_large_screen_shade_header">true</bool>
+
     <!-- Whether to show bottom sheets edge to edge -->
     <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
 
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 7ba080b..e617e88 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"நாளை காலை புளூடூத் இயக்கப்படும்"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ஆடியோவைப் பகிர்"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ஆடியோ பகிரப்படுகிறது"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ஆடியோ பகிர்வு அமைப்புகளுக்குச் செல்லும்"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> பேட்டரி"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"முடிந்தது"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"அமைப்புகள்"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"இயக்கப்பட்டுள்ளது"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ஆன் • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"முடக்கப்பட்டுள்ளது"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"அமையுங்கள்"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"அமைப்புகளில் நிர்வகியுங்கள்"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"சாட்டிலைட், நிலையான இணைப்பு"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"சாட்டிலைட், இணைப்பு கிடைக்கிறது"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"சாட்டிலைட் SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"பணிக் கணக்கு"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"சில வேடிக்கையாக இருந்தாலும் கவனம் தேவை"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner, Android பயனர் இடைமுகத்தை மாற்றவும் தனிப்பயனாக்கவும் கூடுதல் வழிகளை வழங்குகிறது. இந்தப் பரிசோதனைக்குரிய அம்சங்கள் எதிர்கால வெளியீடுகளில் மாற்றப்படலாம், இடைநிறுத்தப்படலாம் அல்லது தோன்றாமல் போகலாம். கவனத்துடன் தொடரவும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index de140af..b45e82b 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"బ్లూటూత్ రేపు ఉదయం ఆన్ అవుతుంది"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"ఆడియోను షేర్ చేయండి"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"ఆడియోను షేర్ చేస్తున్నారు"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"ఆడియో షేరింగ్ సెట్టింగ్‌లను ఎంటర్ చేయండి"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్‌సెట్"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"పూర్తయింది"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"సెట్టింగ్‌లు"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"ఆన్‌లో ఉంది"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"ఆన్ • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ఆఫ్‌లో ఉంది"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"సెటప్ చేయండి"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"సెట్టింగ్‌లలో మేనేజ్ చేయండి"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"శాటిలైట్, కనెక్షన్ బాగుంది"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"శాటిలైట్, కనెక్షన్ అందుబాటులో ఉంది"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ఎమర్జెన్సీ శాటిలైట్ సహాయం"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ఆఫీస్ ప్రొఫైల్‌"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"కొందరికి సరదాగా ఉంటుంది కానీ అందరికీ అలాగే ఉండదు"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"సిస్టమ్ UI ట్యూనర్ Android వినియోగదారు ఇంటర్‌ఫేస్‌ను మెరుగుపరచడానికి మరియు అనుకూలంగా మార్చడానికి మీకు మరిన్ని మార్గాలను అందిస్తుంది. ఈ ప్రయోగాత్మక లక్షణాలు భవిష్యత్తు విడుదలల్లో మార్పుకు లోనవ్వచ్చు, తాత్కాలికంగా లేదా పూర్తిగా నిలిపివేయవచ్చు. జాగ్రత్తగా కొనసాగండి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index e4dc392..252fc05 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -155,7 +155,7 @@
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการรวบรวมปัญหา"</string>
     <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"กำลังบันทึกปัญหา"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"แชร์"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"บันทึกไฟล์บันทึกปัญหาแล้ว"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"จัดเก็บไฟล์บันทึกปัญหาแล้ว"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"แตะเพื่อดู"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"เกิดข้อผิดพลาดในการบันทึกไฟล์บันทึกปัญหา"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"เกิดข้อผิดพลาดในการเริ่มบันทึกปัญหา"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"บลูทูธจะเปิดพรุ่งนี้เช้า"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"แชร์เสียง"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"กำลังแชร์เสียง"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"เข้าสู่การตั้งค่าการแชร์เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"เสร็จสิ้น"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"การตั้งค่า"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"เปิด"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"เปิด • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"ปิด"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"ตั้งค่า"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"จัดการในการตั้งค่า"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ดาวเทียม, การเชื่อมต่อดี"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ดาวเทียม, การเชื่อมต่อที่พร้อมใช้งาน"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ดาวเทียม"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"โปรไฟล์งาน"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"เพลิดเพลินกับบางส่วนแต่ไม่ใช่ทั้งหมด"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ตัวรับสัญญาณ UI ระบบช่วยให้คุณมีวิธีพิเศษในการปรับแต่งและกำหนดค่าส่วนติดต่อผู้ใช้ Android ฟีเจอร์รุ่นทดลองเหล่านี้อาจมีการเปลี่ยนแปลง ขัดข้อง หรือหายไปในเวอร์ชันอนาคต โปรดดำเนินการด้วยความระมัดระวัง"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 632bdab..04f52ea 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Mag-o-on ang Bluetooth bukas ng umaga"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Ibahagi ang audio"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Ibinabahagi ang audio"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"pumasok sa mga setting sa pag-share ng audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> na baterya"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Tapos na"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Mga Setting"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Naka-on"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Naka-on • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Naka-off"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"I-set up"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Pamahalaan sa mga setting"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, malakas ang koneksyon"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, may koneksyon"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profile sa trabaho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Masaya para sa ilan ngunit hindi para sa lahat"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Nagbibigay sa iyo ang Tuner ng System UI ng mga karagdagang paraan upang baguhin at i-customize ang user interface ng Android. Ang mga pang-eksperimentong feature na ito ay maaaring magbago, masira o mawala sa mga pagpapalabas sa hinaharap. Magpatuloy nang may pag-iingat."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index dc317a9..19c9e0c 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -155,7 +155,7 @@
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Sorun toplama oturumuyla ilgili devam eden görev bildirimi"</string>
     <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Kayıt sorunu"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Paylaş"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"Sorun kaydı saklandı"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"Sorun kaydı kaydedildi"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Görüntülemek için dokunun"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"Sorun kaydı saklanırken hata oluştu"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Sorun kaydı başlatılırken hata oluştu"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth yarın sabah açılacak"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Sesi paylaş"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Ses paylaşılıyor"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"Ses paylaşımı ayarlarına gitmek için"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Bitti"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ayarlar"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Açık"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Açık • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Kapalı"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Ayarla"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Ayarlarda yönet"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Uydu, bağlantı güçlü"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Uydu, bağlantı mevcut"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Acil Uydu Bağlantısı"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Bazıları için eğlenceliyken diğerleri için olmayabilir"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistem Kullanıcı Arayüzü Ayarlayıcı, Android kullanıcı arayüzünde değişiklikler yapmanız ve arayüzü özelleştirmeniz için ekstra yollar sağlar. Bu deneysel özellikler değişebilir, bozulabilir veya gelecekteki sürümlerde yer almayabilir. Dikkatli bir şekilde devam edin."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 0d243b7..8af4120 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth увімкнеться завтра вранці"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Поділитись аудіо"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Надсилання аудіо"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"відкрити налаштування надсилання аудіо"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> заряду акумулятора"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Запис екрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Почати"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зупинити"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Запис помилки"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Запис проблеми"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Почати"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Зупинити"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Звіт про помилку"</string>
@@ -391,7 +390,7 @@
     <string name="performance" msgid="6552785217174378320">"Продуктивність"</string>
     <string name="user_interface" msgid="3712869377953950887">"Інтерфейс користувача"</string>
     <string name="thermal" msgid="6758074791325414831">"Нагрівання"</string>
-    <string name="custom" msgid="3337456985275158299">"Власні"</string>
+    <string name="custom" msgid="3337456985275158299">"Указати"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Власні налаштування трасування"</string>
     <string name="restore_default" msgid="5259420807486239755">"Відновити налаштування за умовчанням"</string>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Режим керування однією рукою"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Готово"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Налаштування"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Увімкнено"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Увімк. • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Вимкнено"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Налаштувати"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Керувати в налаштуваннях"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хороше з’єднання із супутником"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступне з’єднання із супутником"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Супутниковий сигнал SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Робочий профіль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Це цікаво, але будьте обачні"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner пропонує нові способи налаштувати та персоналізувати інтерфейс користувача Android. Ці експериментальні функції можуть змінюватися, не працювати чи зникати в майбутніх версіях. Будьте обачні."</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 6872b80..dd27515 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -153,9 +153,9 @@
     <string name="issuerecord_title" msgid="286627115110121849">"ایشو ریکارڈر"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"ایشو ریکارڈنگ پروسیس ہو رہی ہے"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"ایشو کلیکشن سیشن کے لیے جاری اطلاع"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"ریکارڈنگ ایشو"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"مسئلہ ریکارڈ ہو رہا ہے"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"اشتراک کریں"</string>
-    <string name="issuerecord_save_title" msgid="4161043023696751591">"ایشو ریکارڈنگ محفوظ ہو گئی"</string>
+    <string name="issuerecord_save_title" msgid="4161043023696751591">"مسئلے کی ریکارڈنگ محفوظ ہو گئی"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"دیکھنے کیلئے تھپتھپائیں"</string>
     <string name="issuerecord_save_error" msgid="6913040083446722726">"ایشو ریکارڈنگ محفوظ کرنے میں خرابی"</string>
     <string name="issuerecord_start_error" msgid="3402782952722871190">"ایشو ریکارڈنگ شروع کرنے میں خرابی"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"بلوٹوتھ کل صبح آن ہو جائے گا"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"آڈیو کا اشتراک کریں"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"آڈیو کا اشتراک ہو رہا ہے"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"آڈیو کے اشتراک کی ترتیبات درج کریں"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"ہو گیا"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"ترتیبات"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"آن ہے"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"آن ہے • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"آف ہے"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"سیٹ اپ کریں"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ترتیبات میں نظم کریں"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"سیٹلائٹ، کنکشن اچھا ہے"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"سیٹلائٹ، کنکشن دستیاب ہے"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"‏سیٹلائٹ SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"دفتری پروفائل"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"کچھ کیلئے دلچسپ لیکن سبھی کیلئے نہیں"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏سسٹم UI ٹیونر Android صارف انٹر فیس میں ردوبدل کرنے اور اسے حسب ضرورت بنانے کیلئے آپ کو اضافی طریقے دیتا ہے۔ یہ تجرباتی خصوصیات مستقبل کی ریلیزز میں تبدیل ہو سکتی، رک سکتی یا غائب ہو سکتی ہیں۔ احتیاط کے ساتھ آگے بڑھیں۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a652a6d..b0ba287 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth ertaga ertalab yoqiladi"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Audioni ulashish"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Audio ulashuvi yoniq"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"audio ulashuv sozlamalarini kiritish"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
@@ -381,7 +380,7 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Ekran yozuvi"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Boshlash"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Toʻxtatish"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Yozib olishda xato"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Nosozlikni yozib olish"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Boshlash"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Toʻxtatish"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Xatoliklar hisoboti"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Tayyor"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Sozlamalar"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Yoniq"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Yoniq • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Yoqilmagan"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Sozlash"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Sozlamalarda boshqarish"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sputnik, aloqa sifati yaxshi"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sputnik, aloqa mavjud"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Sputnik SOS"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ish profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diqqat!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner yordamida siz Android foydalanuvchi interfeysini tuzatish va o‘zingizga moslashtirishingiz mumkin. Ushbu tajribaviy funksiyalar o‘zgarishi, buzilishi yoki keyingi versiyalarda olib tashlanishi mumkin. Ehtiyot bo‘lib davom eting."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 6f55d15..b6c6967 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -153,7 +153,7 @@
     <string name="issuerecord_title" msgid="286627115110121849">"Trình ghi sự cố"</string>
     <string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Đang xử lý bản ghi sự cố"</string>
     <string name="issuerecord_channel_description" msgid="6142326363431474632">"Thông báo hiển thị liên tục cho một phiên thu thập sự cố"</string>
-    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Ghi sự cố"</string>
+    <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Đang ghi sự cố"</string>
     <string name="issuerecord_share_label" msgid="3992657993619876199">"Chia sẻ"</string>
     <string name="issuerecord_save_title" msgid="4161043023696751591">"Đã lưu bản ghi sự cố"</string>
     <string name="issuerecord_save_text" msgid="1205985304551521495">"Nhấn để xem"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth sẽ bật vào sáng mai"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Chia sẻ âm thanh"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Đang chia sẻ âm thanh"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"mở chế độ cài đặt chia sẻ âm thanh"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> pin"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string>
@@ -381,12 +380,12 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Ghi màn hình"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Bắt đầu"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Dừng"</string>
-    <string name="qs_record_issue_label" msgid="8166290137285529059">"Ghi lại vấn đề"</string>
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"Ghi sự cố"</string>
     <string name="qs_record_issue_start" msgid="2979831312582567056">"Bắt đầu"</string>
     <string name="qs_record_issue_stop" msgid="3531747965741982657">"Dừng"</string>
     <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Báo cáo lỗi"</string>
-    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Bạn gặp loại vấn đề gì khi dùng thiết bị?"</string>
-    <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Chọn loại vấn đề"</string>
+    <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Bạn gặp loại sự cố gì khi dùng thiết bị?"</string>
+    <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Chọn loại sự cố"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ghi màn hình"</string>
     <string name="performance" msgid="6552785217174378320">"Hiệu suất"</string>
     <string name="user_interface" msgid="3712869377953950887">"Giao diện người dùng"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Xong"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Cài đặt"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Đang bật"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Bật • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Đang tắt"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Thiết lập"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Quản lý trong phần cài đặt"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Kết nối vệ tinh tốt"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Hiện có kết nối vệ tinh"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Liên lạc khẩn cấp qua vệ tinh"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Hồ sơ công việc"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Thú vị đối với một số người nhưng không phải tất cả"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Bộ điều hướng giao diện người dùng hệ thống cung cấp thêm cho bạn những cách chỉnh sửa và tùy chỉnh giao diện người dùng Android. Những tính năng thử nghiệm này có thể thay đổi, hỏng hoặc biến mất trong các phiên bản tương lai. Hãy thận trọng khi tiếp tục."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d65bf80..9ce05a3 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"蓝牙将在明天早上开启"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音频"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音频"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"进入音频分享设置"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"完成"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"设置"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"已开启"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"已开启 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"已关闭"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"设置"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"在设置中管理"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"卫星,连接质量良好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"卫星,可连接"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"卫星紧急呼救"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作资料"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"并不适合所有用户"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"系统界面调节工具可让您以更多方式调整及定制 Android 界面。在日后推出的版本中,这些实验性功能可能会变更、失效或消失。操作时请务必谨慎。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 5c25841..e8532be 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"藍牙將於明天上午開啟"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音訊"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音訊"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"輸入音訊分享功能設定"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"完成"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"設定"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"開啟"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"開 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"關閉"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"設定"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"在「設定」中管理"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線質素好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可以連線"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連接"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作設定檔"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"這只是測試版本,並不包含完整功能"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"使用者介面調諧器讓你以更多方法修改和自訂 Android 使用者介面。但請小心,這些實驗功能可能會在日後發佈時更改、分拆或消失。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 76019ab..e049374 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -134,14 +134,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"目前正在錄製「<xliff:g id="APP_NAME">%1$s</xliff:g>」的畫面"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"停止錄製"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"正在分享畫面"</string>
-    <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"要停止分享畫面嗎?"</string>
+    <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"停止分享?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"目前正在與「<xliff:g id="HOST_APP_NAME">%1$s</xliff:g>」分享整個畫面"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"目前正在與某個應用程式分享整個畫面"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"目前正在分享「<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>」的畫面"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"目前正在分享應用程式畫面"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"停止分享"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"正在投放畫面"</string>
-    <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"要停止投放嗎?"</string>
+    <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"停止投放?"</string>
     <string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device" msgid="1474703115926205251">"目前正在將整個畫面投放到「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」"</string>
     <string name="cast_to_other_device_stop_dialog_message_entire_screen" msgid="8419219169553867625">"目前正在將整個畫面投放到鄰近裝置"</string>
     <string name="cast_to_other_device_stop_dialog_message_specific_app_with_device" msgid="2715934698604085519">"目前正在將「<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>」投放到「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」"</string>
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"藍牙會在明天早上開啟"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"分享音訊"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"正在分享音訊"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"進入音訊分享設定"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"完成"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"設定"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"開啟"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"已開啟 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"關閉"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"設定"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"在「設定」中管理"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線品質良好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可連線"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連線"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作資料夾"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"有趣與否,見仁見智"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"系統使用者介面調整精靈可讓你透過其他方式,調整及自訂 Android 使用者介面。這些實驗性功能隨著版本更新可能會變更、損壞或消失,執行時請務必謹慎。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 04fc75d..d85db55 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -308,8 +308,7 @@
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"IBluetooth izovuleka kusasa ekuseni"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"Yabelana ngomsindo"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="3069309588231072128">"Yabelana ngomsindo"</string>
-    <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_accessibility (7604615019302091708) -->
-    <skip />
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility" msgid="7604615019302091708">"faka amasethingi okwabelana ngokuqoshiwe"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string>
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
@@ -436,6 +435,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Kwenziwe"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Amasethingi"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Vuliwe"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Vuliwe • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Valiwe"</string>
     <string name="zen_mode_set_up" msgid="7457957033034460064">"Setha"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Phatha kumasethingi"</string>
@@ -718,6 +718,8 @@
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Isethelayithi, uxhumano oluhle"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Isethelayithi, uxhumano luyatholakala"</string>
     <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Isethelayithi yokuxhumana ngezimo eziphuthumayo"</string>
+    <!-- no translation found for satellite_emergency_only_carrier_text (828510231597991206) -->
+    <skip />
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Iphrofayela yomsebenzi"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kuyajabulisa kwabanye kodwa hhayi bonke"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Isishuni se-UI sesistimu sikunika izindlela ezingeziwe zokuhlobisa nokwenza ngezifiso isixhumanisi sokubona se-Android. Lezi zici zesilingo zingashintsha, zephuke, noma zinyamalale ekukhishweni kwangakusasa. Qhubeka ngokuqaphela."</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 823ff9f..e8fd2ef 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -727,6 +727,10 @@
         .75
     </item>
 
+    <!-- The last x ms of face acquired info messages to analyze to determine
+         whether to show a deferred face auth help message. -->
+    <integer name="config_face_help_msgs_defer_analyze_timeframe">500</integer>
+
     <!-- Which face help messages to surface when fingerprint is also enrolled.
          Message ids correspond with the acquired ids in BiometricFaceConstants -->
     <integer-array name="config_face_help_msgs_when_fingerprint_enrolled">
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd943d0..e6cc6cf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -740,6 +740,8 @@
     <string name="quick_settings_bluetooth_device_connected">Connected</string>
     <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
     <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
+    <!-- QuickSettings: Bluetooth dialog device summary for devices that are capable of audio sharing and switching to active[CHAR LIMIT=NONE]-->
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Tap to switch or share audio</string>
     <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_saved">Saved</string>
     <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 02d39a4..e68da09 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -70,6 +70,7 @@
         "dagger2",
         "jsr330",
         "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
+        "//frameworks/libs/systemui:msdl",
     ],
     resource_dirs: [
         "res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 64fe78d..7ec977a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -218,6 +218,7 @@
     @ViewDebug.ExportedProperty(category="recents")
     public String title;
     @ViewDebug.ExportedProperty(category="recents")
+    @Nullable
     public String titleDescription;
     @ViewDebug.ExportedProperty(category="recents")
     public int colorPrimary;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..121577e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -342,8 +342,7 @@
         // the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
                 || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0
-                || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0
-                || (sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
+                || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
             return false;
         }
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index afd42cb..61f9800 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -134,7 +134,7 @@
     private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
-    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
     private final DevicePolicyManager mDevicePolicyManager;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
@@ -321,7 +321,7 @@
             }
 
             if (KeyguardWmStateRefactor.isEnabled()) {
-                mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+                mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition(
                         "KeyguardSecurityContainerController#finish");
             }
         }
@@ -458,7 +458,7 @@
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             DevicePolicyManager devicePolicyManager,
-            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<DeviceEntryInteractor> deviceEntryInteractor
     ) {
@@ -490,7 +490,7 @@
         mSelectedUserInteractor = selectedUserInteractor;
         mDeviceEntryInteractor = deviceEntryInteractor;
         mJavaAdapter = javaAdapter;
-        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mDevicePolicyManager = devicePolicyManager;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 60fff28..f731186 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -483,22 +483,6 @@
     @VisibleForTesting
     SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
 
-    private static int sCurrentUser;
-
-    @Deprecated
-    public synchronized static void setCurrentUser(int currentUser) {
-        sCurrentUser = currentUser;
-    }
-
-    /**
-     * @deprecated This can potentially return unexpected values in a multi user scenario
-     * as this state is managed by another component. Consider using {@link SelectedUserInteractor}.
-     */
-    @Deprecated
-    public synchronized static int getCurrentUser() {
-        return sCurrentUser;
-    }
-
     @Override
     public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
             List<String> trustGrantedMessages) {
@@ -969,7 +953,7 @@
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
         try {
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             if (userId != authUserId) {
                 mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
@@ -1220,7 +1204,7 @@
             mLogger.d("Aborted successful auth because device is going to sleep.");
             return;
         }
-        final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         if (userId != authUserId) {
             mLogger.logFaceAuthForWrongUser(authUserId);
             return;
@@ -2462,7 +2446,7 @@
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
 
         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
-        int user = mSelectedUserInteractor.getSelectedUserId(true);
+        int user = mSelectedUserInteractor.getSelectedUserId();
         boolean isUserUnlocked = mUserManager.isUserUnlocked(user);
         mLogger.logUserUnlockedInitialState(user, isUserUnlocked);
         mUserIsUnlocked.put(user, isUserUnlocked);
@@ -3827,7 +3811,8 @@
         if (!mSimDatas.containsKey(subId)) {
             refreshSimState(subId, SubscriptionManager.getSlotIndex(subId));
         }
-        return mSimDatas.get(subId).slotId;
+        SimData simData = mSimDatas.get(subId);
+        return simData != null ? simData.slotId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@@ -4081,7 +4066,7 @@
             pw.println("    " + subId + "=" + mServiceStates.get(subId));
         }
         if (isFingerprintSupported()) {
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
@@ -4124,7 +4109,7 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
-            final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+            final int userId = mSelectedUserInteractor.getSelectedUserId();
             pw.println("  Fingerprint state (user=" + userId + ")");
             pw.println("    mFingerprintSensorProperties.isEmpty="
                     + mFingerprintSensorProperties.isEmpty());
@@ -4137,7 +4122,7 @@
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
         }
-        final int userId = mSelectedUserInteractor.getSelectedUserId(true);
+        final int userId = mSelectedUserInteractor.getSelectedUserId();
         final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
         pw.println("    authSinceBoot="
                 + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
new file mode 100644
index 0000000..c944878
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserver.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for tracking the current accessibility gesture list.
+ *
+ * @see Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS
+ */
+@MainThread
+@SysUISingleton
+public class AccessibilityGestureTargetsObserver extends
+        SecureSettingsContentObserver<AccessibilityGestureTargetsObserver.TargetsChangedListener> {
+
+    /** Listener for accessibility gesture targets changes. */
+    public interface TargetsChangedListener {
+
+        /**
+         * Called when accessibility gesture targets changes.
+         *
+         * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS}
+         */
+        void onAccessibilityGestureTargetsChanged(String targets);
+    }
+
+    @Inject
+    public AccessibilityGestureTargetsObserver(Context context, UserTracker userTracker) {
+        super(context, userTracker, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+    }
+
+    @Override
+    void onValueChanged(TargetsChangedListener listener, String value) {
+        listener.onAccessibilityGestureTargetsChanged(value);
+    }
+
+    /** Returns the current string from settings key
+     *  {@link Settings.Secure#ACCESSIBILITY_GESTURE_TARGETS}. */
+    @Nullable
+    public String getCurrentAccessibilityGestureTargets() {
+        return getSettingsValue();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index a093f58..fd06bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -29,7 +29,6 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.Flags
 import com.android.systemui.ambient.touch.TouchHandler.TouchSession
 import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
@@ -37,8 +36,8 @@
 import com.android.systemui.ambient.touch.scrim.ScrimManager
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -63,8 +62,6 @@
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val valueAnimatorCreator: ValueAnimatorCreator,
     private val velocityTrackerFactory: VelocityTrackerFactory,
-    private val lockPatternUtils: LockPatternUtils,
-    private val userTracker: UserTracker,
     private val communalViewModel: CommunalViewModel,
     @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
     private val flingAnimationUtils: FlingAnimationUtils,
@@ -75,7 +72,8 @@
     @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
     private val minBouncerZoneScreenPercentage: Float,
     private val uiEventLogger: UiEventLogger,
-    private val activityStarter: ActivityStarter
+    private val activityStarter: ActivityStarter,
+    private val keyguardInteractor: KeyguardInteractor,
 ) : TouchHandler {
     /** An interface for creating ValueAnimators. */
     interface ValueAnimatorCreator {
@@ -148,7 +146,7 @@
 
                     // If scrolling up and keyguard is not locked, dismiss both keyguard and the
                     // dream since there's no bouncer to show.
-                    if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+                    if (y > e2.y && keyguardInteractor.isKeyguardDismissible.value) {
                         activityStarter.executeRunnableDismissingKeyguard(
                             { centralSurfaces.get().awakenDreams() },
                             /* cancelAction= */ null,
@@ -331,8 +329,8 @@
             return
         }
 
-        // Don't set expansion if the user doesn't have a pin/password set.
-        if (!lockPatternUtils.isSecure(userTracker.userId)) {
+        // Don't set expansion if keyguard is dismissible (i.e. unlocked).
+        if (keyguardInteractor.isKeyguardDismissible.value) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
index 1685f49..4731ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
@@ -25,11 +25,13 @@
  * - startWindow: Window of time on start required before showing the first help message
  * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
  *   the user
+ * - threshold: minimum percentage of frames a message must appear in order to show it
  */
 class FaceHelpMessageDebouncer(
     private val window: Long = DEFAULT_WINDOW_MS,
     private val startWindow: Long = window,
     private val shownFaceMessageFrequencyBoost: Int = 4,
+    private val threshold: Float = 0f,
 ) {
     private val TAG = "FaceHelpMessageDebouncer"
     private var startTime = 0L
@@ -56,7 +58,7 @@
         }
     }
 
-    private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
+    private fun getMostFrequentHelpMessageSurpassingThreshold(): HelpFaceAuthenticationStatus? {
         // freqMap: msgId => frequency
         val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
 
@@ -83,7 +85,25 @@
                     }
                 }
                 ?.key
-        return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+
+        if (msgIdWithHighestFrequency == null) {
+            return null
+        }
+
+        val freq =
+            if (msgIdWithHighestFrequency == lastMessageIdShown) {
+                    freqMap[msgIdWithHighestFrequency]!! - shownFaceMessageFrequencyBoost
+                } else {
+                    freqMap[msgIdWithHighestFrequency]!!
+                }
+                .toFloat()
+
+        return if ((freq / helpFaceAuthStatuses.size.toFloat()) >= threshold) {
+            helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+        } else {
+            Log.v(TAG, "most frequent helpFaceAuthStatus didn't make the threshold: $threshold")
+            null
+        }
     }
 
     fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
@@ -98,14 +118,15 @@
             return null
         }
         removeOldMessages(atTimestamp)
-        val messageToShow = getMostFrequentHelpMessage()
+        val messageToShow = getMostFrequentHelpMessageSurpassingThreshold()
         if (lastMessageIdShown != messageToShow?.msgId) {
             Log.v(
                 TAG,
                 "showMessage previousLastMessageId=$lastMessageIdShown" +
                     "\n\tmessageToShow=$messageToShow " +
                     "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
-                    "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
+                    "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses" +
+                    "\n\tthreshold=$threshold"
             )
             lastMessageIdShown = messageToShow?.msgId
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
index 90d06fb..d382ada 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -17,14 +17,19 @@
 package com.android.systemui.biometrics
 
 import android.content.res.Resources
+import android.os.SystemClock.elapsedRealtime
 import com.android.keyguard.logging.BiometricMessageDeferralLogger
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags.faceMessageDeferUpdate
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
+import dagger.Lazy
 import java.io.PrintWriter
 import java.util.Objects
 import java.util.UUID
@@ -36,7 +41,8 @@
 constructor(
     @Main private val resources: Resources,
     @BiometricLog private val logBuffer: LogBuffer,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val systemClock: Lazy<SystemClock>,
 ) {
     fun create(): FaceHelpMessageDeferral {
         val id = UUID.randomUUID().toString()
@@ -45,6 +51,7 @@
             logBuffer = BiometricMessageDeferralLogger(logBuffer, "FaceHelpMessageDeferral[$id]"),
             dumpManager = dumpManager,
             id = id,
+            systemClock,
         )
     }
 }
@@ -58,14 +65,17 @@
     logBuffer: BiometricMessageDeferralLogger,
     dumpManager: DumpManager,
     val id: String,
+    val systemClock: Lazy<SystemClock>,
 ) :
     BiometricMessageDeferral(
         resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
         resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
         resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
+        resources.getInteger(R.integer.config_face_help_msgs_defer_analyze_timeframe).toLong(),
         logBuffer,
         dumpManager,
         id,
+        systemClock,
     )
 
 /**
@@ -77,10 +87,24 @@
     private val messagesToDefer: Set<Int>,
     private val acquiredInfoToIgnore: Set<Int>,
     private val threshold: Float,
+    private val windowToAnalyzeLastNFrames: Long,
     private val logBuffer: BiometricMessageDeferralLogger,
     dumpManager: DumpManager,
     id: String,
+    private val systemClock: Lazy<SystemClock>,
 ) : Dumpable {
+
+    private val faceHelpMessageDebouncer: FaceHelpMessageDebouncer? =
+        if (faceMessageDeferUpdate()) {
+            FaceHelpMessageDebouncer(
+                window = windowToAnalyzeLastNFrames,
+                startWindow = 0L,
+                shownFaceMessageFrequencyBoost = 0,
+                threshold = threshold,
+            )
+        } else {
+            null
+        }
     private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
     private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
     private var mostFrequentAcquiredInfoToDefer: Int? = null
@@ -97,13 +121,20 @@
         pw.println("messagesToDefer=$messagesToDefer")
         pw.println("totalFrames=$totalFrames")
         pw.println("threshold=$threshold")
+        pw.println("faceMessageDeferUpdateFlagEnabled=${faceMessageDeferUpdate()}")
+        if (faceMessageDeferUpdate()) {
+            pw.println("windowToAnalyzeLastNFrames(ms)=$windowToAnalyzeLastNFrames")
+        }
     }
 
     /** Reset all saved counts. */
     fun reset() {
         totalFrames = 0
-        mostFrequentAcquiredInfoToDefer = null
-        acquiredInfoToFrequency.clear()
+        if (!faceMessageDeferUpdate()) {
+            mostFrequentAcquiredInfoToDefer = null
+            acquiredInfoToFrequency.clear()
+        }
+
         acquiredInfoToHelpString.clear()
         logBuffer.reset()
     }
@@ -137,24 +168,48 @@
             logBuffer.logFrameIgnored(acquiredInfo)
             return
         }
-
         totalFrames++
 
-        val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
-        acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
-        if (
-            messagesToDefer.contains(acquiredInfo) &&
-                (mostFrequentAcquiredInfoToDefer == null ||
-                    newAcquiredInfoCount >
-                        acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
-        ) {
-            mostFrequentAcquiredInfoToDefer = acquiredInfo
+        if (faceMessageDeferUpdate()) {
+            faceHelpMessageDebouncer?.let {
+                val helpFaceAuthStatus =
+                    HelpFaceAuthenticationStatus(
+                        msgId = acquiredInfo,
+                        msg = null,
+                        systemClock.get().elapsedRealtime()
+                    )
+                if (totalFrames == 1) { // first frame
+                    it.startNewFaceAuthSession(helpFaceAuthStatus.createdAt)
+                }
+                it.addMessage(helpFaceAuthStatus)
+            }
+        } else {
+            val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
+            acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
+            if (
+                messagesToDefer.contains(acquiredInfo) &&
+                    (mostFrequentAcquiredInfoToDefer == null ||
+                        newAcquiredInfoCount >
+                            acquiredInfoToFrequency.getOrDefault(
+                                mostFrequentAcquiredInfoToDefer!!,
+                                0
+                            ))
+            ) {
+                mostFrequentAcquiredInfoToDefer = acquiredInfo
+            }
         }
 
         logBuffer.logFrameProcessed(
             acquiredInfo,
             totalFrames,
-            mostFrequentAcquiredInfoToDefer?.toString()
+            if (faceMessageDeferUpdate()) {
+                faceHelpMessageDebouncer
+                    ?.getMessageToShow(systemClock.get().elapsedRealtime())
+                    ?.msgId
+                    .toString()
+            } else {
+                mostFrequentAcquiredInfoToDefer?.toString()
+            }
         )
     }
 
@@ -166,9 +221,16 @@
      *   [threshold] percentage.
      */
     fun getDeferredMessage(): CharSequence? {
-        mostFrequentAcquiredInfoToDefer?.let {
-            if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
-                return acquiredInfoToHelpString[it]
+        if (faceMessageDeferUpdate()) {
+            faceHelpMessageDebouncer?.let {
+                val helpFaceAuthStatus = it.getMessageToShow(systemClock.get().elapsedRealtime())
+                return acquiredInfoToHelpString[helpFaceAuthStatus?.msgId]
+            }
+        } else {
+            mostFrequentAcquiredInfoToDefer?.let {
+                if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
+                    return acquiredInfoToHelpString[it]
+                }
             }
         }
         return null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index cd9b9bc..0b440ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -45,6 +45,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieCompositionFactory
+import com.android.systemui.Flags.bpIconA11y
 import com.android.systemui.biometrics.Utils.ellipsize
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -54,6 +55,7 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.common.ui.view.onTouchListener
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
@@ -330,17 +332,31 @@
 
                 // reuse the icon as a confirm button
                 launch {
-                    viewModel.isIconConfirmButton
-                        .map { isPending ->
-                            when {
-                                isPending && modalities.hasFaceAndFingerprint ->
-                                    View.OnTouchListener { _: View, event: MotionEvent ->
-                                        viewModel.onOverlayTouch(event)
-                                    }
-                                else -> null
+                    if (bpIconA11y()) {
+                        viewModel.isIconConfirmButton.collect { isButton ->
+                            if (isButton) {
+                                iconView.onTouchListener { _: View, event: MotionEvent ->
+                                    viewModel.onOverlayTouch(event)
+                                }
+                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+                            } else {
+                                iconView.setOnTouchListener(null)
+                                iconView.setOnClickListener(null)
                             }
                         }
-                        .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
+                    } else {
+                        viewModel.isIconConfirmButton
+                            .map { isPending ->
+                                when {
+                                    isPending && modalities.hasFaceAndFingerprint ->
+                                        View.OnTouchListener { _: View, event: MotionEvent ->
+                                            viewModel.onOverlayTouch(event)
+                                        }
+                                    else -> null
+                                }
+                            }
+                            .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
+                    }
                 }
 
                 // dismiss prompt when authenticated and confirmed
@@ -358,7 +374,8 @@
                             // Allow icon to be used as confirmation button with udfps and a11y
                             // enabled
                             if (
-                                accessibilityManager.isTouchExplorationEnabled &&
+                                !bpIconA11y() &&
+                                    accessibilityManager.isTouchExplorationEnabled &&
                                     modalities.hasUdfps
                             ) {
                                 iconView.setOnClickListener { viewModel.confirmAuthenticated() }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index 4a358c0..bdd4c16 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -31,6 +31,8 @@
     @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
     @UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
     @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
+    @UiEvent(doc = "Available audio sharing device clicked, do nothing")
+    AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED(1880),
     @UiEvent(doc = "Connected other device clicked to disconnect")
     CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
     @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
@@ -44,8 +46,14 @@
         doc = "Not broadcasting, having one connected, another saved LE audio device is clicked"
     )
     LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED(1719),
+    @Deprecated(
+        "Use case no longer needed",
+        ReplaceWith("LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED")
+    )
     @UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
-    LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720);
+    LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
+    @UiEvent(doc = "Not broadcasting, having two connected, the active LE audio devices is clicked")
+    LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED(1881);
 
     override fun getId() = metricId
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 8b2449a..a8f7fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -29,6 +29,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
 import com.android.systemui.Prefs
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
@@ -271,7 +272,7 @@
         val intent =
             Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
                 putExtra(
-                    ":settings:show_fragment_args",
+                    EXTRA_SHOW_FRAGMENT_ARGUMENTS,
                     Bundle().apply {
                         putString("device_address", deviceItem.cachedBluetoothDevice.address)
                     }
@@ -292,7 +293,16 @@
 
     override fun onAudioSharingButtonClicked(view: View) {
         uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
-        startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+        val intent =
+            Intent(ACTION_AUDIO_SHARING).apply {
+                putExtra(
+                    EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+                    Bundle().apply {
+                        putBoolean(LocalBluetoothLeBroadcast.EXTRA_START_LE_AUDIO_SHARING, true)
+                    }
+                )
+            }
+        startSettingsActivity(intent, view)
     }
 
     private fun cancelJob() {
@@ -320,6 +330,7 @@
     companion object {
         private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
         private const val CONTENT_HEIGHT_PREF_KEY = Prefs.Key.BLUETOOTH_TILE_DIALOG_CONTENT_HEIGHT
+        private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
 
         private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
             if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index a78130f..2ba4c73 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -38,6 +38,7 @@
 enum class DeviceItemType {
     ACTIVE_MEDIA_BLUETOOTH_DEVICE,
     AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+    AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
     AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
     CONNECTED_BLUETOOTH_DEVICE,
     SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 9d82e76..f1894d3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -67,7 +67,7 @@
                     backgroundDispatcher,
                     logger
                 ),
-                NotSharingClickedConnected(
+                NotSharingClickedActive(
                     leAudioProfile,
                     assistantProfile,
                     backgroundDispatcher,
@@ -106,6 +106,12 @@
                     DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
                         uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
                     }
+                    DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+                        // TODO(b/360759048): pop up dialog
+                        uiEventLogger.log(
+                            BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
+                        )
+                    }
                     DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
                         setActive()
                         uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
@@ -238,14 +244,14 @@
             BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED
     }
 
-    private class NotSharingClickedConnected(
+    private class NotSharingClickedActive(
         private val leAudioProfile: LeAudioProfile?,
         private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
         @Background private val backgroundDispatcher: CoroutineDispatcher,
         private val logger: BluetoothTileDialogLogger,
     ) : LaunchSettingsCriteria {
-        // If not broadcasting, having two device connected, clicked on any connected LE audio
-        // devices
+        // If not broadcasting, having two device connected, clicked on the active LE audio
+        // device
         override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
             return withContext(backgroundDispatcher) {
                 val matched =
@@ -259,7 +265,7 @@
                                         logger
                                     )
                                     .size == 2 &&
-                                deviceItem.isActiveOrConnectedLeAudioSupported
+                                deviceItem.isActiveLeAudioSupported
                         }
                     } ?: false
 
@@ -275,7 +281,7 @@
         }
 
         override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
-            BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED
+            BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED
     }
 
     private companion object {
@@ -290,10 +296,8 @@
         val DeviceItem.isNotConnectedLeAudioSupported: Boolean
             get() = type == DeviceItemType.SAVED_BLUETOOTH_DEVICE && isLeAudioSupported
 
-        val DeviceItem.isActiveOrConnectedLeAudioSupported: Boolean
-            get() =
-                (type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE ||
-                    type == DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) && isLeAudioSupported
+        val DeviceItem.isActiveLeAudioSupported: Boolean
+            get() = type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE && isLeAudioSupported
 
         val DeviceItem.isMediaDevice: Boolean
             get() =
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index e846bf7..7280489 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -125,6 +125,37 @@
     }
 }
 
+internal class AvailableAudioSharingMediaDeviceItemFactory(
+    private val localBluetoothManager: LocalBluetoothManager?
+) : AvailableMediaDeviceItemFactory() {
+    override fun isFilterMatched(
+        context: Context,
+        cachedDevice: CachedBluetoothDevice,
+        audioManager: AudioManager
+    ): Boolean {
+        return BluetoothUtils.isAudioSharingEnabled() &&
+            super.isFilterMatched(context, cachedDevice, audioManager) &&
+            BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
+                cachedDevice,
+                localBluetoothManager
+            )
+    }
+
+    override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+            context.getString(
+                R.string.quick_settings_bluetooth_device_audio_sharing_or_switch_active
+            ),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            "",
+            isActive = false
+        )
+    }
+}
+
 internal class ActiveHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
     override fun isFilterMatched(
         context: Context,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 9524496..9114eca 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -118,6 +118,7 @@
         listOf(
             ActiveMediaDeviceItemFactory(),
             AudioSharingMediaDeviceItemFactory(localBluetoothManager),
+            AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager),
             AvailableMediaDeviceItemFactory(),
             ConnectedDeviceItemFactory(),
             SavedDeviceItemFactory()
@@ -127,6 +128,7 @@
         listOf(
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+            DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index c1f7d59..102ae7a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -52,7 +52,9 @@
                         setContent {
                             PlatformTheme {
                                 BouncerContent(
-                                    rememberViewModel { viewModelFactory.create() },
+                                    rememberViewModel("ComposeBouncerViewBinder") {
+                                        viewModelFactory.create()
+                                    },
                                     dialogFactory,
                                 )
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index df50e8f..c67b354 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,7 +39,10 @@
      * being able to attempt to unlock the device.
      */
     val isInputEnabled: StateFlow<Boolean>,
-) : SysUiViewModel() {
+
+    /** Name to use for performance tracing purposes. */
+    val traceName: String,
+) : ExclusiveActivatable() {
 
     private val _animateFailure = MutableStateFlow(false)
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index cfd4f50..e54dc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -38,7 +38,7 @@
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.android.systemui.util.kotlin.Utils.Companion.sample
@@ -79,7 +79,7 @@
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
     private val flags: ComposeBouncerFlags,
-) : SysUiViewModel() {
+) : ExclusiveActivatable() {
     /**
      * A message shown when the user has attempted the wrong credential too many times and now must
      * wait a while before attempting to authenticate again.
@@ -155,7 +155,7 @@
                     emptyFlow()
                 }
             }
-            .collectLatest { messageViewModel -> message.value = messageViewModel }
+            .collect { messageViewModel -> message.value = messageViewModel }
     }
 
     private suspend fun listenForSimBouncerEvents() {
@@ -170,7 +170,7 @@
                     emptyFlow()
                 }
             }
-            .collectLatest {
+            .collect {
                 if (it != null) {
                     message.value = it
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
index 2a27271..2d57e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 
 /**
@@ -46,7 +45,7 @@
                     Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
                 )
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 63b6f01..adc4bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.type
 import androidx.core.graphics.drawable.toBitmap
+import com.android.app.tracing.coroutines.traceCoroutine
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
@@ -33,7 +34,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -62,7 +63,7 @@
     private val pinViewModelFactory: PinBouncerViewModel.Factory,
     private val patternViewModelFactory: PatternBouncerViewModel.Factory,
     private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
-) : SysUiViewModel() {
+) : ExclusiveActivatable() {
     private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
     val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
 
@@ -146,7 +147,7 @@
                     .map(::getChildViewModel)
                     .collectLatest { childViewModelOrNull ->
                         _authMethodViewModel.value = childViewModelOrNull
-                        childViewModelOrNull?.activate()
+                        childViewModelOrNull?.let { traceCoroutine(it.traceName) { it.activate() } }
                     }
             }
 
@@ -159,7 +160,7 @@
             launch {
                 userSwitcher.selectedUser
                     .map { it.image.toBitmap() }
-                    .collectLatest { _selectedUserImage.value = it }
+                    .collect { _selectedUserImage.value = it }
             }
 
             launch {
@@ -186,34 +187,32 @@
                                 )
                             }
                     }
-                    .collectLatest { _userSwitcherDropdown.value = it }
+                    .collect { _userSwitcherDropdown.value = it }
             }
 
             launch {
                 combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
-                    .collectLatest { _dialogViewModel.value = it }
+                    .collect { _dialogViewModel.value = it }
             }
 
-            launch {
-                actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it }
-            }
+            launch { actionButtonInteractor.actionButton.collect { _actionButton.value = it } }
 
             launch {
                 authMethodViewModel
                     .map { authMethod -> isSideBySideSupported(authMethod) }
-                    .collectLatest { _isSideBySideSupported.value = it }
+                    .collect { _isSideBySideSupported.value = it }
             }
 
             launch {
                 authMethodViewModel
                     .map { authMethod -> isFoldSplitRequired(authMethod) }
-                    .collectLatest { _isFoldSplitRequired.value = it }
+                    .collect { _isFoldSplitRequired.value = it }
             }
 
             launch {
                 message.isLockoutMessagePresent
                     .map { lockoutMessagePresent -> !lockoutMessagePresent }
-                    .collectLatest { _isInputEnabled.value = it }
+                    .collect { _isInputEnabled.value = it }
             }
 
             awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index c91fd6a..2493cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -34,7 +34,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.receiveAsFlow
@@ -53,6 +52,7 @@
     AuthMethodBouncerViewModel(
         interactor = interactor,
         isInputEnabled = isInputEnabled,
+        traceName = "PasswordBouncerViewModel",
     ) {
 
     private val _password = MutableStateFlow("")
@@ -104,11 +104,9 @@
                 combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
                         hasInput && !hasFocus
                     }
-                    .collectLatest { _isTextFieldFocusRequested.value = it }
+                    .collect { _isTextFieldFocusRequested.value = it }
             }
-            launch {
-                selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it }
-            }
+            launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } }
             launch {
                 // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
                 // whenever
@@ -124,7 +122,7 @@
                     ) { selectedUserId, _ ->
                         inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
                     }
-                    .collectLatest { _isImeSwitcherButtonVisible.value = it }
+                    .collect { _isImeSwitcherButtonVisible.value = it }
             }
             awaitCancellation()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 4c02929..0a866b4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -34,7 +34,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
@@ -51,6 +50,7 @@
     AuthMethodBouncerViewModel(
         interactor = interactor,
         isInputEnabled = isInputEnabled,
+        traceName = "PatternBouncerViewModel",
     ) {
 
     /** The number of columns in the dot grid. */
@@ -85,9 +85,7 @@
         coroutineScope {
             launch { super.onActivated() }
             launch {
-                selectedDotSet
-                    .map { it.toList() }
-                    .collectLatest { selectedDotList.value = it.toList() }
+                selectedDotSet.map { it.toList() }.collect { selectedDotList.value = it.toList() }
             }
             awaitCancellation()
         }
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 c611954..df6ca9b 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
@@ -42,7 +42,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -63,6 +62,7 @@
     AuthMethodBouncerViewModel(
         interactor = interactor,
         isInputEnabled = isInputEnabled,
+        traceName = "PinBouncerViewModel",
     ) {
     /**
      * Whether the sim-related UI in the pin view is showing.
@@ -122,7 +122,7 @@
                     } else {
                         interactor.hintedPinLength
                     }
-                    .collectLatest { _hintedPinLength.value = it }
+                    .collect { _hintedPinLength.value = it }
             }
             launch {
                 combine(
@@ -134,17 +134,17 @@
                             isAutoConfirmEnabled = isAutoConfirmEnabled,
                         )
                     }
-                    .collectLatest { _backspaceButtonAppearance.value = it }
+                    .collect { _backspaceButtonAppearance.value = it }
             }
             launch {
                 interactor.isAutoConfirmEnabled
                     .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
-                    .collectLatest { _confirmButtonAppearance.value = it }
+                    .collect { _confirmButtonAppearance.value = it }
             }
             launch {
                 interactor.isPinEnhancedPrivacyEnabled
                     .map { !it }
-                    .collectLatest { _isDigitButtonAnimationEnabled.value = it }
+                    .collect { _isDigitButtonAnimationEnabled.value = it }
             }
             awaitCancellation()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 6757edb..b2d02ed 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -120,7 +120,7 @@
                         Intent.FLAG_ACTIVITY_NEW_TASK,
                         null,
                         activityOptions.toBundle(),
-                        selectedUserInteractor.getSelectedUserId(true),
+                        selectedUserInteractor.getSelectedUserId(),
                     )
                 } catch (e: RemoteException) {
                     Log.w("CameraGestureHelper", "Unable to start camera activity", e)
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index a43447f..aae21b9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -66,7 +66,8 @@
                     @Override
                     public WindowInsets onApplyWindowInsets(@NonNull View view,
                             @NonNull WindowInsets windowInsets) {
-                        Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+                        Insets insets = windowInsets.getInsets(
+                                WindowInsets.Type.systemBars() | WindowInsets.Type.ime());
                         ViewGroup.MarginLayoutParams layoutParams =
                                 (ViewGroup.MarginLayoutParams) view.getLayoutParams();
                         layoutParams.leftMargin = insets.left;
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
index 46db346..208adc2 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -18,6 +18,7 @@
 
 import android.content.pm.PackageInstaller
 import android.os.Handler
+import android.text.TextUtils
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.dagger.SysUISingleton
@@ -63,6 +64,7 @@
                         synchronized(sessions) {
                             sessions.putAll(
                                 packageInstaller.allSessions
+                                    .filter { !TextUtils.isEmpty(it.appPackageName) }
                                     .map { session -> session.toModel() }
                                     .associateBy { it.sessionId }
                             )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 0582cc2..c69cea4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -32,11 +32,14 @@
 import com.android.systemui.keyguard.shared.model.filterState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 
 /**
@@ -54,6 +57,18 @@
     private val dreamManager: DreamManager,
     @Background private val bgScope: CoroutineScope,
 ) : CoreStartable {
+    /** Flow that emits when the dream should be started underneath the glanceable hub. */
+    val startDream =
+        allOf(
+                keyguardTransitionInteractor
+                    .transitionValue(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
+                    .map { it == 1f },
+                not(keyguardInteractor.isDreaming),
+                // TODO(b/362830856): Remove this workaround.
+                keyguardInteractor.isKeyguardShowing,
+            )
+            .filter { it }
+
     @SuppressLint("MissingPermission")
     override fun start() {
         if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
@@ -72,17 +87,10 @@
 
         // Restart the dream underneath the hub in order to support the ability to swipe
         // away the hub to enter the dream.
-        keyguardTransitionInteractor
-            .transition(
-                edge = Edge.create(to = Scenes.Communal),
-                edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GLANCEABLE_HUB)
-            )
-            .filterState(TransitionState.FINISHED)
+        startDream
             .sampleFilter(powerInteractor.isAwake) { isAwake ->
-                dreamManager.canStartDreaming(isAwake)
+                !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
             }
-            .sampleFilter(keyguardInteractor.isDreaming) { isDreaming -> !isDreaming }
-            .filter { !glanceableHubAllowKeyguardWhenDreaming() }
             .onEach { dreamManager.startDream() }
             .launchIn(bgScope)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index ba2b7bf..a33e0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.dagger
 
 import android.content.Context
+import android.content.res.Resources
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.data.backup.CommunalBackupUtils
 import com.android.systemui.communal.data.db.CommunalDatabaseModule
@@ -38,6 +39,8 @@
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -90,6 +93,7 @@
 
     companion object {
         const val LOGGABLE_PREFIXES = "loggable_prefixes"
+        const val LAUNCHER_PACKAGE = "launcher_package"
 
         @Provides
         @Communal
@@ -126,5 +130,12 @@
                 .getStringArray(com.android.internal.R.array.config_loggable_dream_prefixes)
                 .toList()
         }
+
+        /** The package name of the launcher */
+        @Provides
+        @Named(LAUNCHER_PACKAGE)
+        fun provideLauncherPackage(@Main resources: Resources): String {
+            return resources.getString(R.string.launcher_overlayable_package)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
index 86241a5..f77dd58 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -24,6 +24,9 @@
 import com.android.systemui.communal.smartspace.CommunalSmartspaceController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.util.time.SystemClock
 import java.util.concurrent.Executor
@@ -49,8 +52,11 @@
     private val communalSmartspaceController: CommunalSmartspaceController,
     @Main private val uiExecutor: Executor,
     private val systemClock: SystemClock,
+    @CommunalLog logBuffer: LogBuffer,
 ) : CommunalSmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
+    private val logger = Logger(logBuffer, "CommunalSmartspaceRepository")
+
     private val _timers: MutableStateFlow<List<CommunalSmartspaceTimer>> =
         MutableStateFlow(emptyList())
     override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers
@@ -87,6 +93,8 @@
                     remoteViews = target.remoteViews!!,
                 )
             }
+
+        logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() }
     }
 
     override fun startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 2aa6c19..9b96341 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -140,6 +140,10 @@
      */
     val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow()
 
+    private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
+
+    val selectedKey: StateFlow<String?> = _selectedKey.asStateFlow()
+
     /** Whether communal features are enabled. */
     val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
 
@@ -179,6 +183,10 @@
         }
     }
 
+    fun setSelectedKey(key: String?) {
+        _selectedKey.value = key
+    }
+
     /** Whether to show communal when exiting the occluded state. */
     val showCommunalFromOccluded: Flow<Boolean> =
         keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -345,11 +353,10 @@
 
     /** Show the widget editor Activity. */
     fun showWidgetEditor(
-        preselectedKey: String? = null,
         shouldOpenWidgetPickerOnStart: Boolean = false,
     ) {
         communalSceneInteractor.setEditModeState(EditModeState.STARTING)
-        editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
+        editWidgetsActivityStarter.startActivity(shouldOpenWidgetPickerOnStart)
     }
 
     /**
@@ -503,7 +510,7 @@
      * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
      * sized dynamically.
      */
-    fun getOngoingContent(mediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> =
+    val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
         combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media ->
                 val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
 
@@ -519,7 +526,7 @@
                 )
 
                 // Add UMO
-                if (mediaHostVisible && media.hasAnyMediaOrRecommendation) {
+                if (media.hasAnyMediaOrRecommendation) {
                     ongoingContent.add(
                         CommunalContentModel.Umo(
                             createdTimestampMillis = media.createdTimestampMillis,
@@ -607,11 +614,6 @@
         _firstVisibleItemOffset = firstVisibleItemOffset
     }
 
-    fun resetScrollPosition() {
-        _firstVisibleItemIndex = 0
-        _firstVisibleItemOffset = 0
-    }
-
     val firstVisibleItemIndex: Int
         get() = _firstVisibleItemIndex
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index a0b9966..8f756a2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -88,6 +88,7 @@
         keyguardState: KeyguardState? = null,
     ) {
         applicationScope.launch("$TAG#changeScene") {
+            if (currentScene.value == newScene) return@launch
             logger.logSceneChangeRequested(
                 from = currentScene.value,
                 to = newScene,
@@ -108,6 +109,7 @@
     ) {
         applicationScope.launch("$TAG#snapToScene") {
             delay(delayMillis)
+            if (currentScene.value == newScene) return@launch
             logger.logSceneChangeRequested(
                 from = currentScene.value,
                 to = newScene,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 5bbb46d..e04d309 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -182,6 +182,7 @@
     }
 
     private suspend fun finishCurrentTransition() {
+        if (currentTransitionId == null) return
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             1f,
@@ -224,7 +225,7 @@
             collectProgress(transition)
         } else if (transition.toScene == CommunalScenes.Communal) {
             if (currentToState == KeyguardState.GLANCEABLE_HUB) {
-                transitionKtfTo(transitionInteractor.getStartedFromState())
+                transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
             }
             startTransitionToGlanceableHub()
             collectProgress(transition)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 6be94a7..0929d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -55,11 +55,8 @@
     /** Whether widgets are currently being re-ordered. */
     open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
 
-    private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
-
     /** The key of the currently selected item, or null if no item selected. */
-    val selectedKey: StateFlow<String?>
-        get() = _selectedKey
+    val selectedKey: StateFlow<String?> = communalInteractor.selectedKey
 
     private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
@@ -226,7 +223,7 @@
 
     /** Set the key of the currently selected item */
     fun setSelectedKey(key: String?) {
-        _selectedKey.value = key
+        communalInteractor.setSelectedKey(key)
     }
 
     /** Invoked once touches inside the lazy grid are consumed */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 16788d1..65f0679 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -29,6 +29,7 @@
 import android.view.accessibility.AccessibilityManager
 import androidx.activity.result.ActivityResultLauncher
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.communal.dagger.CommunalModule.Companion.LAUNCHER_PACKAGE
 import com.android.systemui.communal.data.model.CommunalWidgetCategories
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -81,6 +82,7 @@
     @Application private val context: Context,
     private val accessibilityManager: AccessibilityManager,
     private val packageManager: PackageManager,
+    @Named(LAUNCHER_PACKAGE) private val launcherPackage: String,
 ) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
 
     private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -185,7 +187,6 @@
     /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
     suspend fun onOpenWidgetPicker(
         resources: Resources,
-        packageManager: PackageManager,
         activityLauncher: ActivityResultLauncher<Intent>
     ): Boolean =
         withContext(backgroundDispatcher) {
@@ -196,7 +197,7 @@
                 ) {
                     it.providerInfo
                 }
-            getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let {
+            getWidgetPickerActivityIntent(resources, excludeList)?.let {
                 try {
                     activityLauncher.launch(it)
                     return@withContext true
@@ -209,18 +210,10 @@
 
     private fun getWidgetPickerActivityIntent(
         resources: Resources,
-        packageManager: PackageManager,
         excludeList: ArrayList<AppWidgetProviderInfo>
     ): Intent? {
-        val packageName =
-            getLauncherPackageName(packageManager)
-                ?: run {
-                    Log.e(TAG, "Couldn't resolve launcher package name")
-                    return@getWidgetPickerActivityIntent null
-                }
-
         return Intent(Intent.ACTION_PICK).apply {
-            setPackage(packageName)
+            setPackage(launcherPackage)
             putExtra(
                 EXTRA_DESIRED_WIDGET_WIDTH,
                 resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
@@ -247,16 +240,6 @@
         }
     }
 
-    private fun getLauncherPackageName(packageManager: PackageManager): String? {
-        return packageManager
-            .resolveActivity(
-                Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) },
-                PackageManager.MATCH_DEFAULT_ONLY
-            )
-            ?.activityInfo
-            ?.packageName
-    }
-
     /** Sets whether edit mode is currently open */
     fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 4c762dc..d69ba1b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -99,7 +99,7 @@
 
     private val logger = Logger(logBuffer, "CommunalViewModel")
 
-    private val _isMediaHostVisible =
+    private val isMediaHostVisible =
         conflatedCallbackFlow {
                 val callback = { visible: Boolean ->
                     trySend(visible)
@@ -117,12 +117,26 @@
                 mediaHost.updateViewVisibility()
                 emit(mediaHost.visible)
             }
+            .distinctUntilChanged()
             .onEach { logger.d({ "_isMediaHostVisible: $bool1" }) { bool1 = it } }
             .flowOn(mainDispatcher)
 
     /** Communal content saved from the previous emission when the flow is active (not "frozen"). */
     private var frozenCommunalContent: List<CommunalContentModel>? = null
 
+    private val ongoingContent =
+        combine(
+            isMediaHostVisible,
+            communalInteractor.ongoingContent.onEach { mediaHost.updateViewVisibility() }
+        ) { mediaVisible, ongoingContent ->
+            if (mediaVisible) {
+                ongoingContent
+            } else {
+                // Media is not visible, don't show UMO
+                ongoingContent.filterNot { it is CommunalContentModel.Umo }
+            }
+        }
+
     @OptIn(ExperimentalCoroutinesApi::class)
     private val latestCommunalContent: Flow<List<CommunalContentModel>> =
         tutorialInteractor.isTutorialAvailable
@@ -130,8 +144,6 @@
                 if (isTutorialMode) {
                     return@flatMapLatest flowOf(communalInteractor.tutorialContent)
                 }
-                val ongoingContent =
-                    _isMediaHostVisible.flatMapLatest { communalInteractor.getOngoingContent(it) }
                 combine(
                     ongoingContent,
                     communalInteractor.widgetContent,
@@ -254,6 +266,7 @@
             expandedMatchesParentHeight = true
             showsOnlyActiveMedia = false
             falsingProtectionNeeded = false
+            disablePagination = true
             init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
         }
     }
@@ -262,7 +275,7 @@
         shouldOpenWidgetPickerOnStart: Boolean,
     ) {
         persistScrollPosition()
-        communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+        communalInteractor.showWidgetEditor(shouldOpenWidgetPickerOnStart)
     }
 
     override fun onDismissCtaTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 93c3a63..d84dc20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -68,7 +68,6 @@
     companion object {
         private const val TAG = "EditWidgetsActivity"
         private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
-        const val EXTRA_PRESELECTED_KEY = "preselected_key"
         const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
     }
 
@@ -219,12 +218,9 @@
         windowInsetsController?.hide(WindowInsets.Type.systemBars())
         window.setDecorFitsSystemWindows(false)
 
-        val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
         shouldOpenWidgetPickerOnStart =
             intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false)
 
-        communalViewModel.setSelectedKey(preselectedKey)
-
         setContent {
             PlatformTheme {
                 Box(
@@ -274,11 +270,7 @@
 
     private fun onOpenWidgetPicker() {
         lifecycleScope.launch {
-            communalViewModel.onOpenWidgetPicker(
-                resources,
-                packageManager,
-                addWidgetActivityLauncher
-            )
+            communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index af87f09..63121a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.Intent
 import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_OPEN_WIDGET_PICKER_ON_START
-import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
@@ -27,7 +26,6 @@
 
 interface EditWidgetsActivityStarter {
     fun startActivity(
-        preselectedKey: String? = null,
         shouldOpenWidgetPickerOnStart: Boolean = false,
     )
 }
@@ -39,12 +37,11 @@
     private val activityStarter: ActivityStarter,
 ) : EditWidgetsActivityStarter {
 
-    override fun startActivity(preselectedKey: String?, shouldOpenWidgetPickerOnStart: Boolean) {
+    override fun startActivity(shouldOpenWidgetPickerOnStart: Boolean) {
         activityStarter.startActivityDismissingKeyguard(
             Intent(applicationContext, EditWidgetsActivity::class.java)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
                 .apply {
-                    preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) }
                     putExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, shouldOpenWidgetPickerOnStart)
                 },
             /* onlyProvisioned = */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0363a68..411cbd5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -66,6 +66,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagDependenciesModule;
 import com.android.systemui.flags.FlagsModule;
+import com.android.systemui.haptics.msdl.dagger.MSDLModule;
 import com.android.systemui.inputmethod.InputMethodModule;
 import com.android.systemui.keyboard.KeyboardModule;
 import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
@@ -231,6 +232,7 @@
         MediaProjectionTaskSwitcherModule.class,
         MediaRouterModule.class,
         MotionToolModule.class,
+        MSDLModule.class,
         PeopleHubModule.class,
         PeopleModule.class,
         PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index e07b5c2..21922ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -256,7 +256,7 @@
                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                         mConfig.wakeScreenGestureAvailable()
                           && mConfig.alwaysOnEnabled(
-                                  mSelectedUserInteractor.getSelectedUserId(true)),
+                                  mSelectedUserInteractor.getSelectedUserId()),
                         DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         false /* reports touch coordinates */,
                         false /* touchscreen */
@@ -297,7 +297,7 @@
 
     private boolean udfpsLongPressConfigured() {
         return mUdfpsEnrolled
-                && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true))
+                && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId())
                 || mScreenOffUdfpsEnabled);
     }
 
@@ -477,7 +477,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
-            if (userId != mSelectedUserInteractor.getSelectedUserId(true)) {
+            if (userId != mSelectedUserInteractor.getSelectedUserId()) {
                 return;
             }
             for (TriggerSensor s : mTriggerSensors) {
@@ -703,13 +703,13 @@
         }
 
         protected boolean enabledBySetting() {
-            if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) {
+            if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId())) {
                 return false;
             } else if (TextUtils.isEmpty(mSetting)) {
                 return true;
             }
             return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
-                    mSelectedUserInteractor.getSelectedUserId(true)) != 0;
+                    mSelectedUserInteractor.getSelectedUserId()) != 0;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 4a9f741..dd08d32 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -251,7 +251,7 @@
             return;
         }
         mNotificationPulseTime = SystemClock.elapsedRealtime();
-        if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) {
+        if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId())) {
             runIfNotNull(onPulseSuppressedListener);
             mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 24ac542..9051745 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -255,10 +255,8 @@
         return mAnimator as AnimatorSet
     }
 
-    /** Starts the dream content and dream overlay exit animations. */
-    fun wakeUp() {
+    fun onWakeUp() {
         cancelAnimations()
-        mOverlayStateController.setExitAnimationsRunning(true)
     }
 
     /** Cancels the dream content and dream overlay animations, if they're currently running. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index bf6d266..3dd2561 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -381,16 +381,17 @@
     }
 
     /**
-     * Handle the dream waking up and run any necessary animations.
+     * Handle the dream waking up.
      */
-    public void wakeUp() {
+    public void onWakeUp() {
+        // TODO(b/361872929): clean up this bool as it doesn't do anything anymore
         // When swiping causes wakeup, do not run any animations as the dream should exit as soon
         // as possible.
         if (mWakingUpFromSwipe) {
             return;
         }
 
-        mDreamOverlayAnimationsController.wakeUp();
+        mDreamOverlayAnimationsController.onWakeUp();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4c22763..e3f740e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -24,12 +24,11 @@
 import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.graphics.drawable.ColorDrawable;
-import android.service.dreams.DreamActivity;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -65,6 +64,7 @@
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -89,6 +89,8 @@
         LifecycleOwner {
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final TaskMatcher DREAM_TYPE_MATCHER =
+            new TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_DREAM);
 
     // The Context is used to construct the hosting constraint layout and child overlay views.
     private final Context mContext;
@@ -141,10 +143,6 @@
     private final TouchInsetManager mTouchInsetManager;
     private final LifecycleOwner mLifecycleOwner;
 
-
-
-    private ComponentName mCurrentBlockedGestureDreamActivityComponent;
-
     private final ArrayList<Job> mFlows = new ArrayList<>();
 
     /**
@@ -221,16 +219,122 @@
         }
     };
 
-    private final DreamOverlayStateController.Callback mExitAnimationFinishedCallback =
-            new DreamOverlayStateController.Callback() {
-                @Override
-                public void onStateChanged() {
-                    if (!mStateController.areExitAnimationsRunning()) {
-                        mStateController.removeCallback(mExitAnimationFinishedCallback);
-                        resetCurrentDreamOverlayLocked();
+    /**
+     * {@link ResetHandler} protects resetting {@link DreamOverlayService} by making sure reset
+     * requests are processed before subsequent actions proceed. Requests themselves are also
+     * ordered between each other as well to ensure actions are correctly sequenced.
+     */
+    private final class ResetHandler {
+        @FunctionalInterface
+        interface Callback {
+            void onComplete();
+        }
+
+        private record Info(Callback callback, String source) {}
+
+        private final ArrayList<Info> mPendingCallbacks = new ArrayList<>();
+
+        DreamOverlayStateController.Callback mStateCallback =
+                new DreamOverlayStateController.Callback() {
+                    @Override
+                    public void onStateChanged() {
+                        process(true);
                     }
+                };
+
+        /**
+         * Called from places where there is no need to wait for the reset to complete. This still
+         * will defer the reset until it is okay to reset and also sequences the request with
+         * others.
+         */
+        public void reset(String source) {
+            reset(()-> {}, source);
+        }
+
+        /**
+         * Invoked to request a reset with a callback that will fire after reset if it is deferred.
+         *
+         * @return {@code true} if the reset happened immediately, {@code false} if it was deferred
+         * and will fire later, invoking the callback.
+         */
+        public boolean reset(Callback callback, String source) {
+            // Always add listener pre-emptively
+            if (mPendingCallbacks.isEmpty()) {
+                mStateController.addCallback(mStateCallback);
+            }
+
+            final Info info = new Info(callback, source);
+            mPendingCallbacks.add(info);
+            process(false);
+
+            boolean processed = !mPendingCallbacks.contains(info);
+
+            if (!processed) {
+                Log.d(TAG, "delayed resetting from: " + source);
+            }
+
+            return processed;
+        }
+
+        private void resetInternal() {
+            // This ensures the container view of the current dream is removed before
+            // the controller is potentially reset.
+            removeContainerViewFromParentLocked();
+
+            if (mStarted && mWindow != null) {
+                try {
+                    mWindow.clearContentView();
+                    mWindowManager.removeView(mWindow.getDecorView());
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Error removing decor view when resetting overlay", e);
                 }
-            };
+            }
+
+            mStateController.setOverlayActive(false);
+            mStateController.setLowLightActive(false);
+            mStateController.setEntryAnimationsFinished(false);
+
+            if (mDreamOverlayContainerViewController != null) {
+                mDreamOverlayContainerViewController.destroy();
+                mDreamOverlayContainerViewController = null;
+            }
+
+            if (mTouchMonitor != null) {
+                mTouchMonitor.destroy();
+                mTouchMonitor = null;
+            }
+
+            mWindow = null;
+
+            // Always unregister the any set DreamActivity from being blocked from gestures.
+            mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+                    GestureInteractor.Scope.Global);
+
+            mStarted = false;
+        }
+
+        private boolean canReset() {
+            return !mStateController.areExitAnimationsRunning();
+        }
+
+        private void process(boolean fromDelayedCallback) {
+            while (canReset() && !mPendingCallbacks.isEmpty()) {
+                final Info callbackInfo = mPendingCallbacks.removeFirst();
+                resetInternal();
+                callbackInfo.callback.onComplete();
+
+                if (fromDelayedCallback) {
+                    Log.d(TAG, "reset overlay (delayed) for " + callbackInfo.source);
+                }
+            }
+
+            if (mPendingCallbacks.isEmpty()) {
+                mStateController.removeCallback(mStateCallback);
+            }
+        }
+    }
+
+    private final ResetHandler mResetHandler = new ResetHandler();
 
     private final DreamOverlayStateController mStateController;
 
@@ -344,10 +448,8 @@
 
         mExecutor.execute(() -> {
             setLifecycleStateLocked(Lifecycle.State.DESTROYED);
-
-            resetCurrentDreamOverlayLocked();
-
             mDestroyed = true;
+            mResetHandler.reset("destroying");
         });
 
         mDispatcher.onServicePreSuperOnDestroy();
@@ -387,7 +489,10 @@
             // Reset the current dream overlay before starting a new one. This can happen
             // when two dreams overlap (briefly, for a smoother dream transition) and both
             // dreams are bound to the dream overlay service.
-            resetCurrentDreamOverlayLocked();
+            if (!mResetHandler.reset(() -> onStartDream(layoutParams),
+                    "starting with dream already started")) {
+                return;
+            }
         }
 
         mDreamOverlayContainerViewController =
@@ -399,7 +504,7 @@
 
         // If we are not able to add the overlay window, reset the overlay.
         if (!addOverlayWindowLocked(layoutParams)) {
-            resetCurrentDreamOverlayLocked();
+            mResetHandler.reset("couldn't add window while starting");
             return;
         }
 
@@ -420,7 +525,11 @@
         mStarted = true;
 
         updateRedirectWakeup();
-        updateBlockedGestureDreamActivityComponent();
+
+        if (!isDreamInPreviewMode()) {
+            mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+                    GestureInteractor.Scope.Global);
+        }
     }
 
     private void updateRedirectWakeup() {
@@ -431,21 +540,9 @@
         redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
     }
 
-    private void updateBlockedGestureDreamActivityComponent() {
-        // TODO(b/343815446): We should not be crafting this ActivityInfo ourselves. It should be
-        // in a common place, Such as DreamActivity itself.
-        final ActivityInfo info = new ActivityInfo();
-        info.name = DreamActivity.class.getName();
-        info.packageName = getDreamComponent().getPackageName();
-        mCurrentBlockedGestureDreamActivityComponent = info.getComponentName();
-
-        mGestureInteractor.addGestureBlockedActivity(mCurrentBlockedGestureDreamActivityComponent,
-                GestureInteractor.Scope.Global);
-    }
-
     @Override
     public void onEndDream() {
-        resetCurrentDreamOverlayLocked();
+        mResetHandler.reset("ending dream");
     }
 
     @Override
@@ -481,7 +578,7 @@
     public void onWakeUp() {
         if (mDreamOverlayContainerViewController != null) {
             mDreamOverlayCallbackController.onWakeUp();
-            mDreamOverlayContainerViewController.wakeUp();
+            mDreamOverlayContainerViewController.onWakeUp();
         }
     }
 
@@ -576,49 +673,4 @@
         Log.w(TAG, "Removing dream overlay container view parent!");
         parentView.removeView(containerView);
     }
-
-    private void resetCurrentDreamOverlayLocked() {
-        if (mStateController.areExitAnimationsRunning()) {
-            mStateController.addCallback(mExitAnimationFinishedCallback);
-            return;
-        }
-
-        // This ensures the container view of the current dream is removed before
-        // the controller is potentially reset.
-        removeContainerViewFromParentLocked();
-
-        if (mStarted && mWindow != null) {
-            try {
-                mWindow.clearContentView();
-                mWindowManager.removeView(mWindow.getDecorView());
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Error removing decor view when resetting overlay", e);
-            }
-        }
-
-        mStateController.setOverlayActive(false);
-        mStateController.setLowLightActive(false);
-        mStateController.setEntryAnimationsFinished(false);
-
-        if (mDreamOverlayContainerViewController != null) {
-            mDreamOverlayContainerViewController.destroy();
-            mDreamOverlayContainerViewController = null;
-        }
-
-        if (mTouchMonitor != null) {
-            mTouchMonitor.destroy();
-            mTouchMonitor = null;
-        }
-
-        mWindow = null;
-
-        // Always unregister the any set DreamActivity from being blocked from gestures.
-        if (mCurrentBlockedGestureDreamActivityComponent != null) {
-            mGestureInteractor.removeGestureBlockedActivity(
-                    mCurrentBlockedGestureDreamActivityComponent, GestureInteractor.Scope.Global);
-            mCurrentBlockedGestureDreamActivityComponent = null;
-        }
-
-        mStarted = false;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index befd822..d547de2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -43,6 +43,8 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
 
 typealias FragmentInfoCallback = (TaskFragmentInfo) -> Unit
 
@@ -68,14 +70,18 @@
     }
 
     private val fragmentToken = Binder()
-    private val organizer: TaskFragmentOrganizer =
-        object : TaskFragmentOrganizer(executor) {
 
-                override fun onTransactionReady(transaction: TaskFragmentTransaction) {
-                    handleTransactionReady(transaction)
-                }
-            }
-            .apply { registerOrganizer(true /* isSystemOrganizer */) }
+    class Organizer(val component: WeakReference<TaskFragmentComponent>, executor: Executor) :
+        TaskFragmentOrganizer(executor) {
+        override fun onTransactionReady(transaction: TaskFragmentTransaction) {
+            component.get()?.handleTransactionReady(transaction)
+        }
+    }
+
+    private val organizer: TaskFragmentOrganizer =
+        Organizer(WeakReference(this), executor).apply {
+            registerOrganizer(true /* isSystemOrganizer */)
+        }
 
     private fun handleTransactionReady(transaction: TaskFragmentTransaction) {
         val resultT = WindowContainerTransaction()
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index e88349b2..87eeebf 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -16,8 +16,16 @@
 
 package com.android.systemui.education.domain.interactor
 
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventListener
+import android.hardware.input.KeyGestureEvent
 import com.android.systemui.CoreStartable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
@@ -25,10 +33,14 @@
 import com.android.systemui.education.shared.model.EducationInfo
 import com.android.systemui.education.shared.model.EducationUiType
 import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.time.Clock
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.hours
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
@@ -41,10 +53,12 @@
     @Background private val backgroundScope: CoroutineScope,
     private val contextualEducationInteractor: ContextualEducationInteractor,
     private val userInputDeviceRepository: UserInputDeviceRepository,
+    private val inputManager: InputManager,
     @EduClock private val clock: Clock,
 ) : CoreStartable {
 
     companion object {
+        const val TAG = "KeyboardTouchpadEduInteractor"
         const val MAX_SIGNAL_COUNT: Int = 2
         val usageSessionDuration = 72.hours
     }
@@ -52,6 +66,26 @@
     private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
     val educationTriggered = _educationTriggered.asStateFlow()
 
+    private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow {
+        val listener = KeyGestureEventListener { event ->
+            val shortcutType =
+                when (event.keyGestureType) {
+                    KeyGestureEvent.KEY_GESTURE_TYPE_BACK -> BACK
+                    KeyGestureEvent.KEY_GESTURE_TYPE_HOME -> HOME
+                    KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS -> OVERVIEW
+                    KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS
+                    else -> null
+                }
+
+            if (shortcutType != null) {
+                trySendWithFailureLogging(shortcutType, TAG)
+            }
+        }
+
+        inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener)
+        awaitClose { inputManager.unregisterKeyGestureEventListener(listener) }
+    }
+
     override fun start() {
         backgroundScope.launch {
             contextualEducationInteractor.backGestureModelFlow.collect {
@@ -89,6 +123,12 @@
                 }
             }
         }
+
+        backgroundScope.launch {
+            keyboardShortcutTriggered.collect {
+                contextualEducationInteractor.updateShortcutTriggerTime(it)
+            }
+        }
     }
 
     private fun isEducationNeeded(model: GestureEduModel): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index cd0b3f9..6318dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -41,7 +41,6 @@
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import javax.inject.Inject
 
@@ -59,7 +58,6 @@
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
         PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
         NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
-        NotificationsHeadsUpRefactor.token dependsOn NotificationThrottleHun.token
 
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4d75d66..bb73f56 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -55,19 +55,13 @@
     // TODO(b/254512624): Tracking Bug
     @JvmField
     val NOTIFICATION_DRAG_TO_CONTENTS =
-        resourceBooleanFlag(
-            R.bool.config_notificationToContents,
-            "notification_drag_to_contents"
-        )
+        resourceBooleanFlag(R.bool.config_notificationToContents, "notification_drag_to_contents")
 
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
     val BUILDER_EXTRAS_OVERRIDE =
-        sysPropBooleanFlag(
-            "persist.sysui.notification.builder_extras_override",
-            default = true
-        )
+        sysPropBooleanFlag("persist.sysui.notification.builder_extras_override", default = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -81,10 +75,7 @@
     // TODO(b/254512676): Tracking Bug
     @JvmField
     val LOCKSCREEN_CUSTOM_CLOCKS =
-        resourceBooleanFlag(
-            R.bool.config_enableLockScreenCustomClocks,
-            "lockscreen_custom_clocks"
-        )
+        resourceBooleanFlag(R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks")
 
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
@@ -99,10 +90,6 @@
     // TODO(b/255607168): Tracking Bug
     @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1")
 
-    // TODO(b/305984787):
-    @JvmField
-    val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true)
-
     /** Flag to control the revamp of keyguard biometrics progress animation */
     // TODO(b/244313043): Tracking bug
     @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
@@ -125,13 +112,11 @@
 
     /** Whether the long-press gesture to open wallpaper picker is enabled. */
     // TODO(b/266242192): Tracking Bug
-    @JvmField
-    val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
+    @JvmField val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
 
     /** Inflate and bind views upon emitting a blueprint value . */
     // TODO(b/297365780): Tracking Bug
-    @JvmField
-    val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard")
+    @JvmField val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard")
 
     /** Enables UI updates for AI wallpapers in the wallpaper picker. */
     // TODO(b/267722622): Tracking Bug
@@ -145,8 +130,7 @@
     /** Add "Apply" button to wall paper picker's grid preview page. */
     // TODO(b/294866904): Tracking bug.
     @JvmField
-    val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
-            unreleasedFlag("wallpaper_picker_grid_apply_button")
+    val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
@@ -190,10 +174,7 @@
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
-        resourceBooleanFlag(
-            R.bool.config_enableFullscreenUserSwitcher,
-            "full_screen_user_switcher"
-        )
+        resourceBooleanFlag(R.bool.config_enableFullscreenUserSwitcher, "full_screen_user_switcher")
 
     // TODO(b/244064524): Tracking Bug
     @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info")
@@ -212,16 +193,15 @@
     @JvmField val NEW_NETWORK_SLICE_UI = releasedFlag("new_network_slice_ui")
 
     // TODO(b/311222557): Tracking bug
-    val ROAMING_INDICATOR_VIA_DISPLAY_INFO =
-        releasedFlag("roaming_indicator_via_display_info")
+    val ROAMING_INDICATOR_VIA_DISPLAY_INFO = releasedFlag("roaming_indicator_via_display_info")
 
     // TODO(b/308138154): Tracking bug
     val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
         releasedFlag("filter_provisioning_network_subscriptions")
 
     // TODO(b/293863612): Tracking Bug
-    @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
-        releasedFlag("incompatible_charging_battery_icon")
+    @JvmField
+    val INCOMPATIBLE_CHARGING_BATTERY_ICON = releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
     val INSTANT_TETHER = releasedFlag("instant_tether")
@@ -230,8 +210,7 @@
     val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
 
     // TODO(b/290676905): Tracking Bug
-    val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS =
-        releasedFlag("new_shade_carrier_group_mobile_icons")
+    val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = releasedFlag("new_shade_carrier_group_mobile_icons")
 
     // 800 - general visual/theme
     @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet")
@@ -280,8 +259,7 @@
 
     // TODO(b/273509374): Tracking Bug
     @JvmField
-    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
-        releasedFlag("always_show_home_controls_on_dreams")
+    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag("always_show_home_controls_on_dreams")
 
     // 1100 - windowing
     @Keep
@@ -304,9 +282,7 @@
         )
 
     // TODO(b/293252410) : Tracking Bug
-    @JvmField
-    val LOCKSCREEN_ENABLE_LANDSCAPE =
-            unreleasedFlag("lockscreen.enable_landscape")
+    @JvmField val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape")
 
     // 1200 - predictive back
     @Keep
@@ -327,8 +303,7 @@
     val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
 
     // TODO(b/261979569): Tracking Bug
-    val QUICK_TAP_FLOW_FRAMEWORK =
-        unreleasedFlag("quick_tap_flow_framework", teamfood = false)
+    val QUICK_TAP_FLOW_FRAMEWORK = unreleasedFlag("quick_tap_flow_framework", teamfood = false)
 
     // 1500 - chooser aka sharesheet
 
@@ -364,14 +339,12 @@
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
-    val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
-        unreleasedFlag("enable_dark_vignette_when_folding")
+    val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = unreleasedFlag("enable_dark_vignette_when_folding")
 
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
-    val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
-        unreleasedFlag("enable_unfold_status_bar_animations")
+    val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = unreleasedFlag("enable_unfold_status_bar_animations")
 
     // TODO(b/316157842): Tracking Bug
     // Adds extra delay to notifications measure
@@ -415,28 +388,26 @@
         unreleasedFlag("bigpicture_notification_lazy_loading")
 
     // TODO(b/283740863): Tracking Bug
-    @JvmField
-    val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
+    @JvmField val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
 
     // TODO(b/302144438): Tracking Bug
-    @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
-            unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
+    @JvmField
+    val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
+        unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
     val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
 
     /** Enable the share wifi button in Quick Settings internet dialog. */
-    @JvmField
-    val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
+    @JvmField val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
 
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
-    @JvmField
-    val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
+    @JvmField val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
 
     // TODO(b/300995746): Tracking Bug
     /** A resource flag for whether the communal service is enabled. */
     @JvmField
-    val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled,
-        "communal_service_enabled")
+    val COMMUNAL_SERVICE_ENABLED =
+        resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_service_enabled")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
new file mode 100644
index 0000000..5ea96b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/dagger/MSDLModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.dagger
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.Module
+import dagger.Provides
+
+@Module
+object MSDLModule {
+    @Provides
+    @SysUISingleton
+    fun provideMSDLPlayer(@Application context: Context): MSDLPlayer =
+        MSDLPlayer.createPlayer(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index b654307..a20dfa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceAdded
-import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceChange
 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceRemoved
 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.FreshStart
 import com.android.systemui.keyboard.data.model.Keyboard
@@ -78,24 +77,16 @@
     inputDeviceRepository: InputDeviceRepository
 ) : KeyboardRepository {
 
-    private val keyboardsChange: Flow<Pair<Collection<Int>, DeviceChange>> =
-        inputDeviceRepository.deviceChange
-            .map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change }
-            .filter { (_, change) ->
-                when (change) {
-                    FreshStart -> true
-                    is DeviceAdded -> isPhysicalFullKeyboard(change.deviceId)
-                    is DeviceRemoved -> isPhysicalFullKeyboard(change.deviceId)
-                }
-            }
-
     @FlowPreview
     override val newlyConnectedKeyboard: Flow<Keyboard> =
-        keyboardsChange
+        inputDeviceRepository.deviceChange
             .flatMapConcat { (devices, operation) ->
                 when (operation) {
-                    FreshStart -> devices.asFlow()
-                    is DeviceAdded -> flowOf(operation.deviceId)
+                    FreshStart -> devices.filter { id -> isPhysicalFullKeyboard(id) }.asFlow()
+                    is DeviceAdded -> {
+                        if (isPhysicalFullKeyboard(operation.deviceId)) flowOf(operation.deviceId)
+                        else emptyFlow()
+                    }
                     is DeviceRemoved -> emptyFlow()
                 }
             }
@@ -103,8 +94,8 @@
             .flowOn(backgroundDispatcher)
 
     override val isAnyKeyboardConnected: Flow<Boolean> =
-        keyboardsChange
-            .map { (devices, _) -> devices.isNotEmpty() }
+        inputDeviceRepository.deviceChange
+            .map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 871d046..0feb5ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -35,8 +35,6 @@
 import static android.view.WindowManager.TransitionOldType;
 import static android.view.WindowManager.TransitionType;
 
-import static com.android.systemui.Flags.refactorGetCurrentUser;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -79,6 +77,7 @@
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
@@ -319,6 +318,7 @@
     private final WindowManagerOcclusionManager mWmOcclusionManager;
     private final KeyguardEnabledInteractor mKeyguardEnabledInteractor;
     private final KeyguardWakeDirectlyToGoneInteractor mKeyguardWakeDirectlyToGoneInteractor;
+    private final KeyguardDismissInteractor mKeyguardDismissInteractor;
     private final Lazy<FoldGracePeriodProvider> mFoldGracePeriodProvider = new Lazy<>() {
         @Override
         public FoldGracePeriodProvider get() {
@@ -346,7 +346,8 @@
             KeyguardInteractor keyguardInteractor,
             KeyguardEnabledInteractor keyguardEnabledInteractor,
             Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
-            KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
+            KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor,
+            KeyguardDismissInteractor keyguardDismissInteractor) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -375,6 +376,7 @@
         mWmOcclusionManager = windowManagerOcclusionManager;
         mKeyguardEnabledInteractor = keyguardEnabledInteractor;
         mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
+        mKeyguardDismissInteractor = keyguardDismissInteractor;
     }
 
     @Override
@@ -482,7 +484,11 @@
         public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
             trace("dismiss message=" + message);
             checkPermission();
-            mKeyguardViewMediator.dismiss(callback, message);
+            if (KeyguardWmStateRefactor.isEnabled()) {
+                mKeyguardDismissInteractor.dismissKeyguardWithCallback(callback);
+            } else {
+                mKeyguardViewMediator.dismiss(callback, message);
+            }
         }
 
         @Override // Binder interface
@@ -672,9 +678,6 @@
         public void setCurrentUser(int userId) {
             trace("Deprecated/NOT USED: setCurrentUser userId=" + userId);
             checkPermission();
-            if (!refactorGetCurrentUser()) {
-                mKeyguardViewMediator.setCurrentUser(userId);
-            }
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index ba533ce..362e016c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -72,6 +72,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.google.android.msdl.domain.MSDLPlayer
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
@@ -112,6 +113,7 @@
     private val keyguardViewMediator: KeyguardViewMediator,
     private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>,
     @Main private val mainDispatcher: CoroutineDispatcher,
+    private val msdlPlayer: MSDLPlayer,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -219,6 +221,7 @@
                 falsingManager,
                 keyguardViewMediator,
                 mainDispatcher,
+                msdlPlayer,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 17c5977..8c82900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -41,7 +41,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
-import static com.android.systemui.Flags.refactorGetCurrentUser;
 import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
 import static com.android.systemui.Flags.translucentOccludingActivityFix;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
@@ -626,11 +625,9 @@
 
         @Override
         public void onUserSwitching(int userId) {
-            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
+            Log.d(TAG, String.format("onUserSwitching %d", userId));
             synchronized (KeyguardViewMediator.this) {
-                if (refactorGetCurrentUser()) {
-                    notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
-                }
+                notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
                 resetKeyguardDonePendingLocked();
                 dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
@@ -639,7 +636,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
-            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+            Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             // We are calling dismiss again and with a delay as there are race conditions
             // in some scenarios caused by async layout listeners
             mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
@@ -1580,10 +1577,6 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        if (!refactorGetCurrentUser()) {
-            KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
-        }
-
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
         if (isKeyguardServiceEnabled()) {
@@ -2546,19 +2539,6 @@
     }
 
     /**
-     * Update the newUserId. Call while holding WindowManagerService lock.
-     * NOTE: Should only be called by KeyguardViewMediator in response to the user id changing.
-     *
-     * @param newUserId The id of the incoming user.
-     */
-    public void setCurrentUser(int newUserId) {
-        KeyguardUpdateMonitor.setCurrentUser(newUserId);
-        synchronized (this) {
-            notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId));
-        }
-    }
-
-    /**
      * This broadcast receiver should be registered with the SystemUI permission.
      */
     private final BroadcastReceiver mDelayedLockBroadcastReceiver = new BroadcastReceiver() {
@@ -3553,7 +3533,7 @@
                     try {
                         mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
                                 mContext.getPackageName(),
-                                mSelectedUserInteractor.getSelectedUserId(true));
+                                mSelectedUserInteractor.getSelectedUserId());
                     } catch (RemoteException e) {
                         Log.d(TAG, "Failed to force clear flags", e);
                     }
@@ -3591,7 +3571,7 @@
                 try {
                     mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
                             mContext.getPackageName(),
-                            mSelectedUserInteractor.getSelectedUserId(true));
+                            mSelectedUserInteractor.getSelectedUserId());
                 } catch (RemoteException e) {
                     Log.d(TAG, "Failed to set disable flags: " + flags, e);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 443e9876..208a17c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -9,3 +9,4 @@
 jglazier@google.com
 mpietal@google.com
 tsuji@google.com
+yuandizhou@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 180afb2..e89594e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -23,7 +23,7 @@
 import android.view.WindowManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.util.concurrent.Executor
@@ -41,7 +41,7 @@
     private val activityTaskManagerService: IActivityTaskManager,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
 ) {
 
     /**
@@ -148,7 +148,7 @@
         // a transition to GONE. This transition needs to start even if we're not provided an app
         // animation target - it's possible the app is destroyed on creation, etc. but we'll still
         // be unlocking.
-        keyguardTransitionInteractor.startDismissKeyguardTransition(
+        keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
             reason = "Going away remote animation started"
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 1042ae3..e4b0f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -35,7 +35,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,7 +48,6 @@
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class FromAlternateBouncerTransitionInteractor
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e04133..4cf9ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -89,7 +89,7 @@
                 .filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() }
                 .debounce(50L)
                 .sample(
-                    startedKeyguardTransitionStep,
+                    transitionInteractor.startedKeyguardTransitionStep,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
                 )
                 .collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 4666430..2434b29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -117,7 +117,7 @@
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
@@ -132,7 +132,7 @@
     fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) {
         scope.launch {
             if (
-                transitionInteractor.startedKeyguardState.replayCache.last() ==
+                transitionInteractor.startedKeyguardTransitionStep.value.to ==
                     KeyguardState.DREAMING
             ) {
                 if (powerInteractor.detailedWakefulness.value.isAwake()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cd3df07..228e01e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -134,16 +134,12 @@
                 .filterRelevantKeyguardState()
                 .sampleCombine(
                     internalTransitionInteractor.currentTransitionInfoInternal,
-                    finishedKeyguardState,
+                    transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
                     keyguardInteractor.isActiveDreamLockscreenHosted,
                 )
                 .collect {
-                    (
-                        isAbleToDream,
-                        transitionInfo,
-                        finishedKeyguardState,
-                        isActiveDreamLockscreenHosted) ->
-                    val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
+                    (isAbleToDream, transitionInfo, isOnLockscreen, isActiveDreamLockscreenHosted)
+                    ->
                     val isTransitionInterruptible =
                         transitionInfo.to == KeyguardState.LOCKSCREEN &&
                             !invalidFromStates.contains(transitionInfo.from)
@@ -189,7 +185,7 @@
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
             shadeRepository.legacyShadeExpansion
                 .sampleCombine(
-                    startedKeyguardTransitionStep,
+                    transitionInteractor.startedKeyguardTransitionStep,
                     internalTransitionInteractor.currentTransitionInfoInternal,
                     keyguardInteractor.statusBarState,
                     keyguardInteractor.isKeyguardDismissible,
@@ -334,7 +330,7 @@
             listenForSleepTransition(
                 modeOnCanceledFromStartedStep = { startedStep ->
                     if (
-                        transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
+                        keyguardInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
                             startedStep.from == KeyguardState.AOD
                     ) {
                         TransitionModeOnCanceled.REVERSE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index f9ab1bb..bde0f56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -62,16 +62,16 @@
         communalInteractor
             .transitionProgressToScene(toScene)
             .sample(
-                transitionInteractor.startedKeyguardState,
+                transitionInteractor.startedKeyguardTransitionStep,
                 ::Pair,
             )
-            .collect { (transitionProgress, lastStartedState) ->
+            .collect { (transitionProgress, lastStartedStep) ->
                 val id = transitionId
                 if (id == null) {
                     // No transition started.
                     if (
                         transitionProgress is CommunalTransitionProgressModel.Transition &&
-                            lastStartedState == fromState
+                            lastStartedStep.to == fromState
                     ) {
                         transitionId =
                             transitionRepository.startTransition(
@@ -84,7 +84,7 @@
                             )
                     }
                 } else {
-                    if (lastStartedState != toState) {
+                    if (lastStartedStep.to != toState) {
                         return@collect
                     }
                     // An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 628e912..d7e6bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.internal.policy.IKeyguardDismissCallback
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -28,23 +32,30 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
 @SysUISingleton
 class KeyguardDismissInteractor
 @Inject
 constructor(
-    trustRepository: TrustRepository,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
     private val keyguardRepository: KeyguardRepository,
-    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    trustRepository: TrustRepository,
     alternateBouncerInteractor: AlternateBouncerInteractor,
     powerInteractor: PowerInteractor,
-    private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /*
      * Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -127,4 +138,29 @@
     suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
         keyguardRepository.setKeyguardDone(keyguardDoneTiming)
     }
+
+    /**
+     * Dismiss the keyguard (or show the bouncer) and invoke the provided callback once dismissed.
+     *
+     * TODO(b/358412565): Support dismiss messages.
+     */
+    fun dismissKeyguardWithCallback(
+        callback: IKeyguardDismissCallback?,
+    ) {
+        scope.launch {
+            withContext(mainDispatcher) {
+                if (callback != null) {
+                    dismissCallbackRegistry.addCallback(callback)
+                }
+
+                // This will either show the bouncer, or dismiss the keyguard if insecure.
+                // We currently need to request showing the primary bouncer in order to start a
+                // transition to PRIMARY_BOUNCER. Once we refactor that so that starting the
+                // transition is what causes the bouncer to show, we can remove this entire method,
+                // and simply ask KeyguardTransitionInteractor to transition to a bouncer state or
+                // dismiss keyguard.
+                primaryBouncerInteractor.show(true)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
new file mode 100644
index 0000000..c19bbbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardDismissTransitionInteractor
+@Inject
+constructor(
+    private val repository: KeyguardTransitionRepository,
+    private val fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor,
+    private val fromPrimaryBouncerTransitionInteractor: FromPrimaryBouncerTransitionInteractor,
+    private val fromAodTransitionInteractor: FromAodTransitionInteractor,
+    private val fromAlternateBouncerTransitionInteractor: FromAlternateBouncerTransitionInteractor,
+    private val fromDozingTransitionInteractor: FromDozingTransitionInteractor,
+    private val fromOccludedTransitionInteractor: FromOccludedTransitionInteractor,
+) {
+
+    /**
+     * Called to start a transition that will ultimately dismiss the keyguard from the current
+     * state.
+     *
+     * This is called exclusively by sources that can authoritatively say we should be unlocked,
+     * including KeyguardSecurityContainerController and WindowManager.
+     */
+    fun startDismissKeyguardTransition(reason: String = "") {
+        if (SceneContainerFlag.isEnabled) return
+        Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
+        when (val startedState = repository.currentTransitionInfoInternal.value.to) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard()
+            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer()
+            ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer()
+            AOD -> fromAodTransitionInteractor.dismissAod()
+            DOZING -> fromDozingTransitionInteractor.dismissFromDozing()
+            KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.dismissFromOccluded()
+            KeyguardState.GONE ->
+                Log.i(
+                    TAG,
+                    "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
+                )
+            else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
+        }
+    }
+
+    companion object {
+        private val TAG = KeyguardDismissTransitionInteractor::class.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 4aef808..44aafab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -48,7 +48,7 @@
     @Application scope: CoroutineScope,
     val repository: KeyguardRepository,
     val biometricSettingsRepository: BiometricSettingsRepository,
-    transitionInteractor: KeyguardTransitionInteractor,
+    keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
     internalTransitionInteractor: InternalKeyguardTransitionInteractor,
 ) {
 
@@ -94,7 +94,9 @@
                 showKeyguardWhenReenabled
                     .filter { shouldDismiss -> shouldDismiss }
                     .collect {
-                        transitionInteractor.startDismissKeyguardTransition("keyguard disabled")
+                        keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
+                            "keyguard disabled"
+                        )
                     }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index a96d7a8..f6f0cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -36,7 +36,9 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -409,6 +411,12 @@
         }
     }
 
+    /** Which keyguard state to use when the device goes to sleep. */
+    val asleepKeyguardState: StateFlow<KeyguardState> =
+        repository.isAodAvailable
+            .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+            .stateIn(applicationScope, SharingStarted.Eagerly, DOZING)
+
     /**
      * Whether the primary authentication is required for the given user due to lockdown or
      * encryption after reboot.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index 4a8ada7..505c749d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -48,7 +48,6 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -74,9 +73,7 @@
     val isLongPressHandlingEnabled: StateFlow<Boolean> =
         if (isFeatureEnabled()) {
                 combine(
-                    transitionInteractor.finishedKeyguardState.map {
-                        it == KeyguardState.LOCKSCREEN
-                    },
+                    transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
                     repository.isQuickSettingsVisible,
                 ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible ->
                     isFullyTransitionedToLockScreen && !isQuickSettingsVisible
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 31b0bf7..d9c48fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -28,6 +28,7 @@
     private val interactors: Set<TransitionInteractor>,
     private val auditLogger: KeyguardTransitionAuditLogger,
     private val bootInteractor: KeyguardTransitionBootInteractor,
+    private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor,
 ) : CoreStartable {
 
     override fun start() {
@@ -53,6 +54,7 @@
         }
         auditLogger.start()
         bootInteractor.start()
+        statusBarDisableFlagsInteractor.start()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 6ff369e..d11a41e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -22,15 +22,10 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
-import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -47,7 +42,6 @@
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.buffer
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -56,7 +50,6 @@
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.transform
 import kotlinx.coroutines.launch
 
 /** Encapsulates business-logic related to the keyguard transitions. */
@@ -66,16 +59,7 @@
 @Inject
 constructor(
     @Application val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
     private val repository: KeyguardTransitionRepository,
-    private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
-    private val fromPrimaryBouncerTransitionInteractor:
-        dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
-    private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
-    private val fromAlternateBouncerTransitionInteractor:
-        dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
-    private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
-    private val fromOccludedTransitionInteractor: dagger.Lazy<FromOccludedTransitionInteractor>,
     private val sceneInteractor: SceneInteractor,
 ) {
     private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
@@ -126,8 +110,10 @@
             repository.transitions
                 .filter { it.transitionState != TransitionState.CANCELED }
                 .collect { step ->
-                    getTransitionValueFlow(step.from).emit(1f - step.value)
-                    getTransitionValueFlow(step.to).emit(step.value)
+                    val value =
+                        if (step.transitionState == TransitionState.FINISHED) 1f else step.value
+                    getTransitionValueFlow(step.from).emit(1f - value)
+                    getTransitionValueFlow(step.to).emit(value)
                 }
         }
 
@@ -183,8 +169,14 @@
         }
     }
 
-    fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> {
-        return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer)
+    fun transition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<TransitionStep> {
+        return transition(
+            if (SceneContainerFlag.isEnabled || edgeWithoutSceneContainer == null) {
+                edge
+            } else {
+                edgeWithoutSceneContainer
+            }
+        )
     }
 
     /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */
@@ -250,10 +242,10 @@
     }
 
     fun transitionValue(
-        scene: SceneKey,
+        scene: SceneKey? = null,
         stateWithoutSceneContainer: KeyguardState,
     ): Flow<Float> {
-        return if (SceneContainerFlag.isEnabled) {
+        return if (SceneContainerFlag.isEnabled && scene != null) {
             sceneInteractor.transitionProgress(scene)
         } else {
             transitionValue(stateWithoutSceneContainer)
@@ -277,73 +269,10 @@
     }
 
     /** The last [TransitionStep] with a [TransitionState] of STARTED */
-    val startedKeyguardTransitionStep: Flow<TransitionStep> =
-        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
-
-    /** The destination state of the last [TransitionState.STARTED] transition. */
-    @SuppressLint("SharedFlowCreation")
-    val startedKeyguardState: SharedFlow<KeyguardState> =
-        startedKeyguardTransitionStep
-            .map { step -> step.to }
-            .buffer(2, BufferOverflow.DROP_OLDEST)
-            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
-
-    /** The from state of the last [TransitionState.STARTED] transition. */
-    // TODO: is it performant to have several SharedFlows side by side instead of one?
-    @SuppressLint("SharedFlowCreation")
-    val startedKeyguardFromState: SharedFlow<KeyguardState> =
-        startedKeyguardTransitionStep
-            .map { step -> step.from }
-            .buffer(2, BufferOverflow.DROP_OLDEST)
-            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
-
-    /** Which keyguard state to use when the device goes to sleep. */
-    val asleepKeyguardState: StateFlow<KeyguardState> =
-        keyguardRepository.isAodAvailable
-            .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
-            .stateIn(scope, SharingStarted.Eagerly, DOZING)
-
-    /**
-     * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
-     *
-     * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
-     * value when a subsequent transition is STARTED. It will *only* emit once we have finally
-     * FINISHED in a state. This can have unintuitive implications.
-     *
-     * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
-     * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
-     * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
-     * finishes (at which point we'll be FINISHED in LOCKSCREEN).
-     *
-     * Since there's no real limit to how many consecutive transitions can be canceled, it's even
-     * possible for the FINISHED state to be the same as the STARTED state while still
-     * transitioning.
-     *
-     * For example:
-     * 1. We're finished in GONE.
-     * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
-     *    FINISHED in GONE.
-     * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
-     *    LOCKSCREEN transition. We're still FINISHED in GONE.
-     * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
-     *    starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
-     *    STARTED a transition *to* GONE.
-     * 5. We'll emit KeyguardState.GONE again once the transition finishes.
-     *
-     * If you just need to know when we eventually settle into a state, this flow is likely
-     * sufficient. However, if you're having issues with state *during* transitions started after
-     * one or more canceled transitions, you probably need to use [currentKeyguardState].
-     */
-    @SuppressLint("SharedFlowCreation")
-    val finishedKeyguardState: SharedFlow<KeyguardState> =
+    val startedKeyguardTransitionStep: StateFlow<TransitionStep> =
         repository.transitions
-            .transform { step ->
-                if (step.transitionState == TransitionState.FINISHED) {
-                    emit(step.to)
-                }
-            }
-            .buffer(2, BufferOverflow.DROP_OLDEST)
-            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+            .filter { step -> step.transitionState == TransitionState.STARTED }
+            .stateIn(scope, SharingStarted.Eagerly, TransitionStep())
 
     /**
      * The [KeyguardState] we're currently in.
@@ -409,8 +338,7 @@
                     it.from
                 }
             }
-            .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     val isInTransition =
         combine(
@@ -422,33 +350,6 @@
         }
 
     /**
-     * Called to start a transition that will ultimately dismiss the keyguard from the current
-     * state.
-     *
-     * This is called exclusively by sources that can authoritatively say we should be unlocked,
-     * including KeyguardSecurityContainerController and WindowManager.
-     */
-    fun startDismissKeyguardTransition(reason: String = "") {
-        if (SceneContainerFlag.isEnabled) return
-        Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
-        when (val startedState = repository.currentTransitionInfoInternal.value.to) {
-            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
-            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
-            ALTERNATE_BOUNCER ->
-                fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
-            AOD -> fromAodTransitionInteractor.get().dismissAod()
-            DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
-            KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
-            KeyguardState.GONE ->
-                Log.i(
-                    TAG,
-                    "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
-                )
-            else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
-        }
-    }
-
-    /**
      * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
      * completed it.
      *
@@ -506,12 +407,13 @@
 
     fun isFinishedIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
         return if (SceneContainerFlag.isEnabled) {
-            sceneInteractor.transitionState
-                .map { it.isIdle(scene) || it.isTransitioning(from = scene) }
-                .distinctUntilChanged()
-        } else {
-            isFinishedIn(stateWithoutSceneContainer)
-        }
+                sceneInteractor.transitionState.map {
+                    it.isIdle(scene) || it.isTransitioning(from = scene)
+                }
+            } else {
+                isFinishedIn(stateWithoutSceneContainer)
+            }
+            .distinctUntilChanged()
     }
 
     /** Whether we've FINISHED a transition to a state */
@@ -520,17 +422,26 @@
         return finishedKeyguardState.map { it == state }.distinctUntilChanged()
     }
 
+    fun isCurrentlyIn(scene: SceneKey, stateWithoutSceneContainer: KeyguardState): Flow<Boolean> {
+        return if (SceneContainerFlag.isEnabled) {
+                // In STL there is no difference between finished/currentState
+                isFinishedIn(scene, stateWithoutSceneContainer)
+            } else {
+                stateWithoutSceneContainer.checkValidState()
+                currentKeyguardState.map { it == stateWithoutSceneContainer }
+            }
+            .distinctUntilChanged()
+    }
+
     fun getCurrentState(): KeyguardState {
         return currentKeyguardState.replayCache.last()
     }
 
-    fun getStartedFromState(): KeyguardState {
-        return startedKeyguardFromState.replayCache.last()
-    }
-
-    fun getFinishedState(): KeyguardState {
-        return finishedKeyguardState.replayCache.last()
-    }
+    private val finishedKeyguardState: StateFlow<KeyguardState> =
+        repository.transitions
+            .filter { it.transitionState == TransitionState.FINISHED }
+            .map { it.to }
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     companion object {
         private val TAG = KeyguardTransitionInteractor::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index f0bf402..9b8d9ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.settings.SecureSettings
@@ -181,14 +182,20 @@
         scope.launch {
             powerInteractor.detailedWakefulness
                 .distinctUntilChangedBy { it.isAwake() }
-                .sample(transitionInteractor.currentKeyguardState, ::Pair)
-                .collect { (wakefulness, currentState) ->
+                .sample(
+                    transitionInteractor.isCurrentlyIn(
+                        Scenes.Gone,
+                        stateWithoutSceneContainer = KeyguardState.GONE
+                    ),
+                    ::Pair
+                )
+                .collect { (wakefulness, finishedInGone) ->
                     // Save isAwake for use in onDreamingStarted/onDreamingStopped.
                     this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
 
                     // If we're sleeping from GONE, check the timeout and lock instantly settings.
                     // These are not relevant if we're coming from non-GONE states.
-                    if (!isAwake && currentState == KeyguardState.GONE) {
+                    if (!isAwake && finishedInGone) {
                         val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
 
                         // If the screen timed out and went to sleep, and the lock timeout is > 0ms,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
new file mode 100644
index 0000000..e00e33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.IBinder
+import android.os.RemoteException
+import android.provider.DeviceConfig
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.navigation.domain.interactor.NavigationInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Logic around StatusBarService#disableForUser, which is used to disable the home and recents
+ * button in certain device states.
+ *
+ * TODO(b/362313975): Remove post-Flexiglass, this duplicates StatusBarStartable logic.
+ */
+@SysUISingleton
+class StatusBarDisableFlagsInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Application private val applicationContext: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+    private val statusBarService: IStatusBarService,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    selectedUserInteractor: SelectedUserInteractor,
+    deviceConfigInteractor: DeviceConfigInteractor,
+    navigationInteractor: NavigationInteractor,
+    authenticationInteractor: AuthenticationInteractor,
+    powerInteractor: PowerInteractor,
+) : CoreStartable {
+
+    private val disableToken: IBinder = Binder()
+
+    private val disableFlagsForUserId =
+        combine(
+                selectedUserInteractor.selectedUser,
+                keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to },
+                deviceConfigInteractor.property(
+                    namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
+                    name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+                    default = true,
+                ),
+                navigationInteractor.isGesturalMode,
+                authenticationInteractor.authenticationMethod,
+                powerInteractor.detailedWakefulness,
+            ) { values ->
+                val selectedUserId = values[0] as Int
+                val startedState = values[1] as KeyguardState
+                val isShowHomeOverLockscreen = values[2] as Boolean
+                val isGesturalMode = values[3] as Boolean
+                val authenticationMethod = values[4] as AuthenticationMethodModel
+                val wakefulnessModel = values[5] as WakefulnessModel
+                val isOccluded = startedState == KeyguardState.OCCLUDED
+
+                val hideHomeAndRecentsForBouncer =
+                    startedState == KeyguardState.PRIMARY_BOUNCER ||
+                        startedState == KeyguardState.ALTERNATE_BOUNCER
+                val isKeyguardShowing = startedState != KeyguardState.GONE
+                val isPowerGestureIntercepted =
+                    with(wakefulnessModel) {
+                        isAwake() &&
+                            powerButtonLaunchGestureTriggered &&
+                            lastSleepReason == WakeSleepReason.POWER_BUTTON
+                    }
+
+                var flags = StatusBarManager.DISABLE_NONE
+
+                if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) {
+                    if (!isShowHomeOverLockscreen || !isGesturalMode) {
+                        flags = flags or StatusBarManager.DISABLE_HOME
+                    }
+                    flags = flags or StatusBarManager.DISABLE_RECENT
+                }
+
+                if (
+                    isPowerGestureIntercepted &&
+                        isOccluded &&
+                        authenticationMethod.isSecure &&
+                        deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+                ) {
+                    flags = flags or StatusBarManager.DISABLE_RECENT
+                }
+
+                selectedUserId to flags
+            }
+            .distinctUntilChanged()
+
+    @SuppressLint("WrongConstant", "NonInjectedService")
+    override fun start() {
+        if (!KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
+        scope.launch {
+            disableFlagsForUserId.collect { (selectedUserId, flags) ->
+                if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
+                    return@collect
+                }
+
+                withContext(backgroundDispatcher) {
+                    try {
+                        statusBarService.disableForUser(
+                            flags,
+                            disableToken,
+                            applicationContext.packageName,
+                            selectedUserId,
+                        )
+                    } catch (e: RemoteException) {
+                        e.printStackTrace()
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
index 906d586..e404f27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -22,12 +22,12 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import javax.inject.Inject
 
 /**
  * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable
@@ -53,15 +53,15 @@
     val dismissFling =
         shadeRepository.currentFling
             .sample(
-                transitionInteractor.startedKeyguardState,
+                transitionInteractor.startedKeyguardTransitionStep,
                 keyguardInteractor.isKeyguardDismissible,
                 keyguardInteractor.statusBarState,
             )
-            .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) ->
+            .filter { (flingInfo, startedStep, keyguardDismissable, statusBarState) ->
                 flingInfo != null &&
-                        !flingInfo.expand &&
-                        statusBarState != StatusBarState.SHADE_LOCKED &&
-                        startedState == KeyguardState.LOCKSCREEN &&
+                    !flingInfo.expand &&
+                    statusBarState != StatusBarState.SHADE_LOCKED &&
+                    startedStep.to == KeyguardState.LOCKSCREEN &&
                     keyguardDismissable
             }
             .map { (flingInfo, _) -> flingInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index d06ee64..ba12e93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -32,7 +32,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
@@ -62,17 +61,6 @@
 
     abstract fun start()
 
-    /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
-     * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
-     * in continuations on the main thread. We don't want that for classes that inherit from this.
-     */
-    val startedKeyguardTransitionStep =
-        transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
-    // The following are MutableSharedFlows, and do not require flowOn
-    val startedKeyguardState = transitionInteractor.startedKeyguardState
-    val finishedKeyguardState = transitionInteractor.finishedKeyguardState
-    val currentKeyguardState = transitionInteractor.currentKeyguardState
-
     suspend fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
@@ -92,17 +80,6 @@
                     " $fromState. This should never happen - check currentTransitionInfoInternal" +
                     " or use filterRelevantKeyguardState before starting transitions."
             )
-
-            if (fromState == transitionInteractor.finishedKeyguardState.replayCache.last()) {
-                Log.e(
-                    name,
-                    "This transition would not have been ignored prior to ag/26681239, since we " +
-                        "are FINISHED in $fromState (but have since started another transition). " +
-                        "If ignoring this transition has caused a regression, fix it by ensuring " +
-                        "that transitions are exclusively started from the most recently started " +
-                        "state."
-                )
-            }
             return null
         }
 
@@ -207,11 +184,11 @@
         powerInteractor.isAsleep
             .filter { isAsleep -> isAsleep }
             .filterRelevantKeyguardState()
-            .sample(startedKeyguardTransitionStep)
+            .sample(transitionInteractor.startedKeyguardTransitionStep)
             .map(modeOnCanceledFromStartedStep)
             .collect { modeOnCanceled ->
                 startTransitionTo(
-                    toState = transitionInteractor.asleepKeyguardState.value,
+                    toState = keyguardInteractor.asleepKeyguardState.value,
                     modeOnCanceled = modeOnCanceled,
                     ownerReason = "Sleep transition triggered"
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 25b2b7c..ac87400 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -63,10 +63,13 @@
 ) {
     private val defaultSurfaceBehindVisibility =
         combine(
-            transitionInteractor.finishedKeyguardState,
+            transitionInteractor.isFinishedIn(
+                scene = Scenes.Gone,
+                stateWithoutSceneContainer = KeyguardState.GONE
+            ),
             wakeToGoneInteractor.canWakeDirectlyToGone,
-        ) { finishedState, canWakeDirectlyToGone ->
-            isSurfaceVisible(finishedState) || canWakeDirectlyToGone
+        ) { isOnGone, canWakeDirectlyToGone ->
+            isOnGone || canWakeDirectlyToGone
         }
 
     /**
@@ -196,18 +199,20 @@
                         edge = Edge.create(to = Scenes.Gone),
                         edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
                     ),
-                    transitionInteractor.finishedKeyguardState,
+                    transitionInteractor.isFinishedIn(
+                        scene = Scenes.Gone,
+                        stateWithoutSceneContainer = KeyguardState.GONE
+                    ),
                     surfaceBehindInteractor.isAnimatingSurface,
                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
-                ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+                ) { isInTransitionToGone, isOnGone, isAnimatingSurface, notifLaunchRunning ->
                     // Using the animation if we're animating it directly, or if the
                     // ActivityLaunchAnimator is in the process of animating it.
                     val animationsRunning = isAnimatingSurface || notifLaunchRunning
                     // We may still be animating the surface after the keyguard is fully GONE, since
                     // some animations (like the translation spring) are not tied directly to the
                     // transition step amount.
-                    isInTransitionToGone ||
-                        (finishedState == KeyguardState.GONE && animationsRunning)
+                    isInTransitionToGone || (isOnGone && animationsRunning)
                 }
                 .distinctUntilChanged()
         }
@@ -248,7 +253,7 @@
                         // transition. Same for waking directly to gone, due to the lockscreen being
                         // disabled or because the device was woken back up before the lock timeout
                         // duration elapsed.
-                        KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+                        false
                     } else if (canWakeDirectlyToGone) {
                         // Never show the lockscreen if we can wake directly to GONE. This means
                         // that the lock timeout has not yet elapsed, or the keyguard is disabled.
@@ -274,8 +279,7 @@
                         // *not* play the going away animation or related animations.
                         false
                     } else {
-                        // Otherwise, use the visibility of the current state.
-                        KeyguardState.lockscreenVisibleInState(currentState)
+                        currentState != KeyguardState.GONE
                     }
                 }
                 .distinctUntilChanged()
@@ -302,10 +306,4 @@
                     !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode)
             }
             .distinctUntilChanged()
-
-    companion object {
-        fun isSurfaceVisible(state: KeyguardState): Boolean {
-            return !KeyguardState.lockscreenVisibleInState(state)
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index b850095..ffd7812 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -116,7 +116,7 @@
         } else {
             val targetState =
                 if (idle.currentScene == Scenes.Lockscreen) {
-                    transitionInteractor.getStartedFromState()
+                    transitionInteractor.startedKeyguardTransitionStep.value.from
                 } else {
                     UNDEFINED
                 }
@@ -155,7 +155,7 @@
                 val currentToState =
                     internalTransitionInteractor.currentTransitionInfoInternal.value.to
                 if (currentToState == UNDEFINED) {
-                    transitionKtfTo(transitionInteractor.getStartedFromState())
+                    transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
                 }
             }
             startTransitionFromLockscreen()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 24db3c2..080ddfd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -156,12 +156,6 @@
 
     companion object {
 
-        /** Whether the lockscreen is visible when we're FINISHED in the given state. */
-        fun lockscreenVisibleInState(state: KeyguardState): Boolean {
-            // TODO(b/349784682): Transform deprecated states for Flexiglass
-            return state != GONE
-        }
-
         /**
          * Whether the device is awake ([PowerInteractor.isAwake]) when we're FINISHED in the given
          * keyguard state.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 89851db..a7a8321 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,7 @@
 import android.annotation.SuppressLint
 import android.graphics.Point
 import android.graphics.Rect
+import android.os.VibrationAttributes
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import android.view.View
@@ -40,6 +41,7 @@
 import com.android.app.tracing.coroutines.launch
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.systemui.Flags.msdlFeedback
 import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
@@ -79,6 +81,9 @@
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
 import kotlin.math.min
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
@@ -112,6 +117,7 @@
         falsingManager: FalsingManager?,
         keyguardViewMediator: KeyguardViewMediator?,
         mainImmediateDispatcher: CoroutineDispatcher,
+        msdlPlayer: MSDLPlayer?,
     ): DisposableHandle {
         val disposables = DisposableHandles()
         val childViews = mutableMapOf<Int, View>()
@@ -351,21 +357,43 @@
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
                             deviceEntryHapticsInteractor.playSuccessHaptic.collect {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    HapticFeedbackConstants.CONFIRM,
-                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                                )
+                                if (msdlFeedback()) {
+                                    val properties =
+                                        object : InteractionProperties {
+                                            override val vibrationAttributes: VibrationAttributes =
+                                                VibrationAttributes.createForUsage(
+                                                    VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+                                                )
+                                        }
+                                    msdlPlayer?.playToken(MSDLToken.UNLOCK, properties)
+                                } else {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.CONFIRM,
+                                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+                                    )
+                                }
                             }
                         }
 
                         launch {
                             deviceEntryHapticsInteractor.playErrorHaptic.collect {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    HapticFeedbackConstants.REJECT,
-                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                                )
+                                if (msdlFeedback()) {
+                                    val properties =
+                                        object : InteractionProperties {
+                                            override val vibrationAttributes: VibrationAttributes =
+                                                VibrationAttributes.createForUsage(
+                                                    VibrationAttributes.USAGE_HARDWARE_FEEDBACK
+                                                )
+                                        }
+                                    msdlPlayer?.playToken(MSDLToken.FAILURE, properties)
+                                } else {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.REJECT,
+                                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+                                    )
+                                }
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 9ccfb50..f581a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -418,6 +418,7 @@
                     null, // falsing manager not required for preview mode
                     null, // keyguard view mediator is not required for preview mode
                     mainDispatcher,
+                    null,
                 )
         }
         rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 0032c2f..e2ad4635 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToDozingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
@@ -205,6 +206,12 @@
 
     @Binds
     @IntoSet
+    abstract fun occludedToDozing(
+        impl: OccludedToDozingTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun occludedToLockscreen(
         impl: OccludedToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 3e3cbd0..b5d9e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -70,13 +70,11 @@
 
     fun onRemovedFromWindow() {
         statusBarKeyguardViewManager.hideAlternateBouncer(false)
-        primaryBouncerInteractor.setDismissAction(null, null)
-        dismissCallbackRegistry.notifyDismissCancelled()
     }
 
     fun onBackRequested() {
         statusBarKeyguardViewManager.hideAlternateBouncer(false)
-        primaryBouncerInteractor.setDismissAction(null, null)
         dismissCallbackRegistry.notifyDismissCancelled()
+        primaryBouncerInteractor.setDismissAction(null, null)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 6f8389f..9f68210 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -59,6 +59,7 @@
     alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel,
     dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+    occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
 ) {
     val color: Flow<Int> =
         deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
@@ -103,6 +104,7 @@
                         dreamingToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         primaryBouncerToLockscreenTransitionViewModel
                             .deviceEntryBackgroundViewAlpha,
+                        occludedToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                     )
                     .merge()
                     .onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 06b76b3..87c32a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -53,10 +53,10 @@
 ) {
     private val isShowingAodOrDozing: Flow<Boolean> =
         combine(
-            transitionInteractor.startedKeyguardState,
+            transitionInteractor.startedKeyguardTransitionStep,
             transitionInteractor.transitionValue(KeyguardState.DOZING),
-        ) { startedKeyguardState, dozingTransitionValue ->
-            startedKeyguardState == KeyguardState.AOD || dozingTransitionValue == 1f
+        ) { startedKeyguardStep, dozingTransitionValue ->
+            startedKeyguardStep.to == KeyguardState.AOD || dozingTransitionValue == 1f
         }
 
     private fun getColor(usingBackgroundProtection: Boolean): Int {
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 5ce1b5e..d3bb4f5 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
@@ -83,8 +83,8 @@
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
     private val showingAlternateBouncer: Flow<Boolean> =
-        transitionInteractor.startedKeyguardState.map { keyguardState ->
-            keyguardState == KeyguardState.ALTERNATE_BOUNCER
+        transitionInteractor.startedKeyguardTransitionStep.map { keyguardStep ->
+            keyguardStep.to == KeyguardState.ALTERNATE_BOUNCER
         }
     private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) }
     private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index c885c9a..fe4ebfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -85,9 +85,7 @@
     private val previewMode = MutableStateFlow(PreviewMode())
 
     private val showingLockscreen: Flow<Boolean> =
-        transitionInteractor.finishedKeyguardState.map { keyguardState ->
-            keyguardState == KeyguardState.LOCKSCREEN
-        }
+        transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN)
 
     /** The only time the expansion is important is while lockscreen is actively displayed */
     private val shadeExpansionAlpha =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index a96869d..ebdcaa0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -132,8 +132,8 @@
     val burnInModel = _burnInModel.asStateFlow()
 
     val burnInLayerVisibility: Flow<Int> =
-        keyguardTransitionInteractor.startedKeyguardState
-            .filter { it == AOD || it == LOCKSCREEN }
+        keyguardTransitionInteractor.startedKeyguardTransitionStep
+            .filter { it.to == AOD || it.to == LOCKSCREEN }
             .map { VISIBLE }
 
     val goneToAodTransition =
@@ -333,16 +333,17 @@
                     .transitionValue(LOCKSCREEN)
                     .map { it > 0f }
                     .onStart { emit(false) },
-                keyguardTransitionInteractor.finishedKeyguardState.map {
-                    KeyguardState.lockscreenVisibleInState(it)
-                },
+                keyguardTransitionInteractor.isFinishedIn(
+                    scene = Scenes.Gone,
+                    stateWithoutSceneContainer = GONE
+                ),
                 deviceEntryInteractor.isBypassEnabled,
                 areNotifsFullyHiddenAnimated(),
                 isPulseExpandingAnimated(),
             ) { flows ->
                 val goneToAodTransitionRunning = flows[0] as Boolean
                 val isOnLockscreen = flows[1] as Boolean
-                val onKeyguard = flows[2] as Boolean
+                val isOnGone = flows[2] as Boolean
                 val isBypassEnabled = flows[3] as Boolean
                 val notifsFullyHidden = flows[4] as AnimatedValue<Boolean>
                 val pulseExpanding = flows[5] as AnimatedValue<Boolean>
@@ -352,8 +353,7 @@
                     // animation is playing, in which case we want them to be visible if we're
                     // animating in the AOD UI and will be switching to KEYGUARD shortly.
                     goneToAodTransitionRunning ||
-                        (!onKeyguard &&
-                            !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
+                        (isOnGone && !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
                         AnimatedValue.NotAnimating(false)
                     else ->
                         zip(notifsFullyHidden, pulseExpanding) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3e6dd8e..adb63b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSize
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -40,7 +40,6 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
@@ -59,7 +58,7 @@
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
     private val occlusionInteractor: SceneContainerOcclusionInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
-) : SysUiViewModel() {
+) : ExclusiveActivatable() {
     @VisibleForTesting val clockSize = clockInteractor.clockSize
 
     val isUdfpsVisible: Boolean
@@ -91,13 +90,13 @@
                             end = end,
                         )
                     }
-                    .collectLatest { _unfoldTranslations.value = it }
+                    .collect { _unfoldTranslations.value = it }
             }
 
             launch {
                 occlusionInteractor.isOccludingActivityShown
                     .map { !it }
-                    .collectLatest { _isContentVisible.value = it }
+                    .collect { _isContentVisible.value = it }
             }
 
             awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
index 7383f57..2819e61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
@@ -35,7 +35,6 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -100,7 +99,7 @@
                     }
                 }
             }
-            .collectLatest { setActions(it) }
+            .collect { setActions(it) }
     }
 
     private fun swipeDownFromTop(pointerCount: Int): Swipe {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 1314e88..6adf3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -67,7 +67,7 @@
         var leaveShadeOpen = false
 
         return transitionAnimation.sharedFlow(
-            duration = 200.milliseconds,
+            duration = 80.milliseconds,
             onStart = {
                 leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
                 startAlpha = viewState.alpha()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index e64c614..c0b9efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -23,7 +23,9 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.transitions.FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,10 +53,18 @@
                 edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
             )
 
+    private val alphaForAnimationStep: (Float) -> Float =
+        when {
+            SceneContainerFlag.isEnabled -> { step ->
+                    1f - Math.min((step / FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION), 1f)
+                }
+            else -> { step -> 1f - step }
+        }
+
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            onStep = { 1f - it }
+            onStep = alphaForAnimationStep
         )
 
     val lockscreenAlpha: Flow<Float> = shortcutsAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
index af01930..4fb2b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
@@ -17,15 +17,19 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
 
 /**
  * Breaks down OCCLUDED->DOZING transition into discrete steps for corresponding views to consume.
@@ -35,8 +39,9 @@
 class OccludedToDozingTransitionViewModel
 @Inject
 constructor(
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION,
@@ -50,4 +55,17 @@
             duration = 250.milliseconds,
             onStep = { it },
         )
+
+    val deviceEntryBackgroundViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+            ->
+            if (udfpsEnrolledAndEnabled) {
+                transitionAnimation.immediatelyTransitionTo(1f)
+            } else {
+                emptyFlow()
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
index e45d537..708b408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt
@@ -34,7 +34,9 @@
 ) {
     /** When the last keyguard state transition started, was the shade fully expanded? */
     private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> =
-        transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded)
+        transitionInteractor.startedKeyguardTransitionStep.sample(
+            shadeInteractor.isAnyFullyExpanded
+        )
 
     /**
      * Decide which flow to use depending on the shade expansion state at the start of the last
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
index bd3d40b..c1768a4 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
+import com.android.app.tracing.coroutines.traceCoroutine
 
 /** Defines interface for classes that can be activated to do coroutine work. */
 interface Activatable {
@@ -66,13 +67,19 @@
  *
  * If the [key] changes, the old [Activatable] is deactivated and a new one will be instantiated,
  * activated, and returned.
+ *
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
  */
 @Composable
 fun <T : Activatable> rememberActivated(
+    traceName: String,
     key: Any = Unit,
     factory: () -> T,
 ): T {
     val instance = remember(key) { factory() }
-    LaunchedEffect(instance) { instance.activate() }
+    LaunchedEffect(instance) { traceCoroutine(traceName) { instance.activate() } }
     return instance
 }
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
deleted file mode 100644
index 03476ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
+++ /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 com.android.systemui.lifecycle
-
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.launch
-
-/**
- * A base [Activatable] with the following characteristics:
- * 1. **Can be concurrently activated by no more than one owner.** A previous call to [activate]
- *    must be canceled before a new call to [activate] can be made. Trying to call [activate] while
- *    already active will fail with an error
- * 2. **Can manage child [Activatable]s**. See [addChild] and [removeChild]. Added children
- *    automatically track the activation state of the parent such that when the parent is active,
- *    the children are active and vice-versa. Children are also retained such that deactivating the
- *    parent and reactivating it also cancels and reactivates the children.
- */
-abstract class BaseActivatable : Activatable {
-
-    private val _isActive = AtomicBoolean(false)
-
-    var isActive: Boolean
-        get() = _isActive.get()
-        private set(value) {
-            _isActive.set(value)
-        }
-
-    final override suspend fun activate(): Nothing {
-        val allowed = _isActive.compareAndSet(false, true)
-        check(allowed) { "Cannot activate an already active activatable!" }
-
-        coroutineScope {
-            try {
-                launch { manageChildren() }
-                onActivated()
-            } finally {
-                isActive = false
-            }
-        }
-    }
-
-    /**
-     * Notifies that the [Activatable] has been activated.
-     *
-     * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
-     * its state fresh and/or perform side-effects.
-     *
-     * The method suspends and doesn't return until all work required by the object is finished. In
-     * most cases, it's expected for the work to remain ongoing forever so this method will forever
-     * suspend its caller until the coroutine that called it is canceled.
-     *
-     * Implementations could follow this pattern:
-     * ```kotlin
-     * override suspend fun onActivated(): Nothing {
-     *     coroutineScope {
-     *         launch { ... }
-     *         launch { ... }
-     *         launch { ... }
-     *     }
-     * }
-     * ```
-     *
-     * @see activate
-     */
-    protected abstract suspend fun onActivated(): Nothing
-
-    private val newChildren = Channel<Activatable>(Channel.BUFFERED)
-    private val jobByChild: MutableMap<Activatable, Job> by lazy { mutableMapOf() }
-
-    private suspend fun manageChildren(): Nothing {
-        coroutineScope {
-            // Reactivate children that were added during a previous activation:
-            jobByChild.keys.forEach { child -> jobByChild[child] = launch { child.activate() } }
-
-            // Process requests to add more children:
-            newChildren.receiveAsFlow().collect { newChild ->
-                removeChildInternal(newChild)
-                jobByChild[newChild] = launch { newChild.activate() }
-            }
-
-            awaitCancellation()
-        }
-    }
-
-    fun addChild(child: Activatable) {
-        newChildren.trySend(child)
-    }
-
-    fun removeChild(child: Activatable) {
-        removeChildInternal(child)
-    }
-
-    private fun removeChildInternal(child: Activatable) {
-        jobByChild.remove(child)?.cancel()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
new file mode 100644
index 0000000..0837398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/ExclusiveActivatable.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.lifecycle
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * A base [Activatable] that can only be activated by a single owner (hence "exclusive"). A previous
+ * call to [activate] must be canceled before a new call to [activate] can be made. Trying to call
+ * [activate] while already active will result in a runtime error.
+ */
+abstract class ExclusiveActivatable : Activatable {
+
+    private val _isActive = AtomicBoolean(false)
+
+    protected var isActive: Boolean
+        get() = _isActive.get()
+        private set(value) {
+            _isActive.set(value)
+        }
+
+    final override suspend fun activate(): Nothing {
+        val allowed = _isActive.compareAndSet(false, true)
+        check(allowed) { "Cannot activate an already active ExclusiveActivatable!" }
+
+        try {
+            onActivated()
+        } finally {
+            isActive = false
+        }
+    }
+
+    /**
+     * Notifies that the [Activatable] has been activated.
+     *
+     * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+     * its state fresh and/or perform side-effects.
+     *
+     * The method suspends and doesn't return until all work required by the object is finished. In
+     * most cases, it's expected for the work to remain ongoing forever so this method will forever
+     * suspend its caller until the coroutine that called it is canceled.
+     *
+     * Implementations could follow this pattern:
+     * ```kotlin
+     * override suspend fun onActivated(): Nothing {
+     *     coroutineScope {
+     *         launch { ... }
+     *         launch { ... }
+     *         launch { ... }
+     *         awaitCancellation()
+     *     }
+     * }
+     * ```
+     *
+     * @see activate
+     */
+    protected abstract suspend fun onActivated(): Nothing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
new file mode 100644
index 0000000..df1394b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.lifecycle
+
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.StateFactoryMarker
+import com.android.app.tracing.coroutines.launch
+import com.android.app.tracing.coroutines.traceCoroutine
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Keeps snapshot/Compose [State]s up-to-date.
+ *
+ * ```kotlin
+ * val hydrator = Hydrator()
+ * val state: Int by hydrator.hydratedStateOf(upstreamFlow)
+ *
+ * override suspend fun activate(): Nothing {
+ *     hydrator.activate()
+ * }
+ * ```
+ */
+class Hydrator(
+    /**
+     * A name for performance tracing purposes.
+     *
+     * Please use a short string literal that's easy to find in code search. Try to avoid
+     * concatenation or templating.
+     */
+    private val traceName: String,
+) : ExclusiveActivatable() {
+
+    private val children = mutableListOf<NamedActivatable>()
+
+    /**
+     * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
+     *
+     * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
+     *   that's unique enough and easy enough to find in code search; this should help correlate
+     *   performance findings with actual code. One recommendation: prefer whole string literals
+     *   instead of some complex concatenation or templating scheme.
+     * @param source The upstream [StateFlow] to collect from; values emitted to it will be
+     *   automatically set on the returned [State].
+     */
+    @StateFactoryMarker
+    fun <T> hydratedStateOf(
+        traceName: String,
+        source: StateFlow<T>,
+    ): State<T> {
+        return hydratedStateOf(
+            traceName = traceName,
+            initialValue = source.value,
+            source = source,
+        )
+    }
+
+    /**
+     * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
+     *
+     * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
+     *   that's unique enough and easy enough to find in code search; this should help correlate
+     *   performance findings with actual code. One recommendation: prefer whole string literals
+     *   instead of some complex concatenation or templating scheme. Use `null` to disable
+     *   performance tracing for this state.
+     * @param initialValue The first value to place on the [State]
+     * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
+     *   set on the returned [State].
+     */
+    @StateFactoryMarker
+    fun <T> hydratedStateOf(
+        traceName: String?,
+        initialValue: T,
+        source: Flow<T>,
+    ): State<T> {
+        check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." }
+
+        val mutableState = mutableStateOf(initialValue)
+        children.add(
+            NamedActivatable(
+                traceName = traceName,
+                activatable =
+                    object : ExclusiveActivatable() {
+                        override suspend fun onActivated(): Nothing {
+                            source.collect { mutableState.value = it }
+                            awaitCancellation()
+                        }
+                    },
+            )
+        )
+        return mutableState
+    }
+
+    override suspend fun onActivated() = coroutineScope {
+        traceCoroutine(traceName) {
+            children.forEach { child ->
+                if (child.traceName != null) {
+                    launch(spanName = child.traceName) { child.activatable.activate() }
+                } else {
+                    launch { child.activatable.activate() }
+                }
+            }
+            awaitCancellation()
+        }
+    }
+
+    private data class NamedActivatable(
+        val traceName: String?,
+        val activatable: Activatable,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 979eaef..508b04e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -18,88 +18,56 @@
 
 import android.view.View
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.snapshots.StateFactoryMarker
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import com.android.app.tracing.coroutines.traceCoroutine
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
-/** Base class for all System UI view-models. */
-abstract class SysUiViewModel : BaseActivatable() {
-
-    /**
-     * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
-     *
-     * @param source The upstream [StateFlow] to collect from; values emitted to it will be
-     *   automatically set on the returned [State].
-     */
-    @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        source: StateFlow<T>,
-    ): State<T> {
-        return hydratedStateOf(
-            initialValue = source.value,
-            source = source,
-        )
+/**
+ * Returns a remembered view-model of the type [T]. If the returned instance is also an
+ * [Activatable], it's automatically kept active until this composable leaves the composition; if
+ * the [key] changes, the old view-model is deactivated and a new one will be instantiated,
+ * activated, and returned.
+ *
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
+ */
+@Composable
+fun <T> rememberViewModel(
+    traceName: String,
+    key: Any = Unit,
+    factory: () -> T,
+): T {
+    val instance = remember(key) { factory() }
+    if (instance is Activatable) {
+        LaunchedEffect(instance) { traceCoroutine(traceName) { instance.activate() } }
     }
-
-    /**
-     * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
-     *
-     * @param initialValue The first value to place on the [State]
-     * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
-     *   set on the returned [State].
-     */
-    @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        initialValue: T,
-        source: Flow<T>,
-    ): State<T> {
-        val mutableState = mutableStateOf(initialValue)
-        addChild(
-            object : BaseActivatable() {
-                override suspend fun onActivated(): Nothing {
-                    source.collect { mutableState.value = it }
-                    awaitCancellation()
-                }
-            }
-        )
-        return mutableState
-    }
-
-    override suspend fun onActivated(): Nothing {
-        awaitCancellation()
-    }
+    return instance
 }
 
 /**
- * Returns a remembered [SysUiViewModel] of the type [T] that's automatically kept active until this
- * composable leaves the composition.
+ * Invokes [block] in a new coroutine with a new view-model that is automatically activated whenever
+ * `this` [View]'s Window's [WindowLifecycleState] is at least at [minWindowLifecycleState], and is
+ * automatically canceled once that is no longer the case.
  *
- * If the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
- * activated, and returned.
+ * The [traceName] is used for coroutine performance tracing purposes. Please try to use a label
+ * that's unique enough and easy enough to find in code search; this should help correlate
+ * performance findings with actual code. One recommendation: prefer whole string literals instead
+ * of some complex concatenation or templating scheme.
  */
-@Composable
-fun <T : SysUiViewModel> rememberViewModel(
-    key: Any = Unit,
-    factory: () -> T,
-): T = rememberActivated(key, factory)
-
-/**
- * Invokes [block] in a new coroutine with a new [SysUiViewModel] that is automatically activated
- * whenever `this` [View]'s Window's [WindowLifecycleState] is at least at
- * [minWindowLifecycleState], and is automatically canceled once that is no longer the case.
- */
-suspend fun <T : SysUiViewModel> View.viewModel(
+suspend fun <T> View.viewModel(
+    traceName: String,
     minWindowLifecycleState: WindowLifecycleState,
     factory: () -> T,
     block: suspend CoroutineScope.(T) -> Unit,
 ): Nothing =
     repeatOnWindowLifecycle(minWindowLifecycleState) {
         val instance = factory()
-        launch { instance.activate() }
+        if (instance is Activatable) {
+            launch { traceCoroutine(traceName) { instance.activate() } }
+        }
         block(instance)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
index d60f14c..b0abdb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CommunalTouchLog.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.log.dagger
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import javax.inject.Qualifier
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+/** A [com.android.systemui.log.LogBuffer] for communal touch-handling logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CommunalTouchLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5cae58a..ed76646 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -382,6 +382,16 @@
         return factory.create("MediaLog", 20);
     }
 
+    /**
+     * Provides a buffer for media device changes
+     */
+    @Provides
+    @SysUISingleton
+    @MediaDeviceLog
+    public static LogBuffer providesMediaDeviceLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaDeviceLog", 50);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
@@ -618,6 +628,16 @@
     }
 
     /**
+     * Provides a {@link LogBuffer} for communal touch-handling logs.
+     */
+    @Provides
+    @SysUISingleton
+    @CommunalTouchLog
+    public static LogBuffer provideCommunalTouchLogBuffer(LogBufferFactory factory) {
+        return factory.create("CommunalTouchLog", 250);
+    }
+
+    /**
      * Provides a {@link TableLogBuffer} for communal-related logs.
      */
     @Provides
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
similarity index 66%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
index d60f14c..06bd269 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaDeviceLog.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.log.dagger
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import javax.inject.Qualifier
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+/** A [LogBuffer] for [com.android.systemui.media.controls.domain.pipeline.MediaDeviceLogger] */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaDeviceLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
index 9c29bab..ed5080d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -51,9 +51,8 @@
         fun providesMediaDataManager(
             legacyProvider: Provider<LegacyMediaDataManagerImpl>,
             newProvider: Provider<MediaCarouselInteractor>,
-            mediaFlags: MediaFlags,
         ): MediaDataManager {
-            return if (mediaFlags.isSceneContainerEnabled()) {
+            return if (SceneContainerFlag.isEnabled) {
                 newProvider.get()
             } else {
                 legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index 70189b7..378a147 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -30,6 +30,7 @@
 import androidx.media.utils.MediaConstants
 import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
 import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.MediaControlDrawables
 import com.android.systemui.media.controls.shared.model.MediaAction
 import com.android.systemui.media.controls.shared.model.MediaButton
 import com.android.systemui.plugins.ActivityStarter
@@ -58,14 +59,13 @@
     val playOrPause =
         if (isConnectingState(state.state)) {
             // Spinner needs to be animating to render anything. Start it here.
-            val drawable =
-                context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+            val drawable = MediaControlDrawables.getProgress(context)
             (drawable as Animatable).start()
             MediaAction(
                 drawable,
                 null, // no action to perform when clicked
                 context.getString(R.string.controls_media_button_connecting),
-                context.getDrawable(R.drawable.ic_media_connecting_container),
+                MediaControlDrawables.getConnecting(context),
                 // Specify a rebind id to prevent the spinner from restarting on later binds.
                 com.android.internal.R.drawable.progress_small_material
             )
@@ -153,23 +153,23 @@
     return when (action) {
         PlaybackState.ACTION_PLAY -> {
             MediaAction(
-                context.getDrawable(R.drawable.ic_media_play),
+                MediaControlDrawables.getPlayIcon(context),
                 { controller.transportControls.play() },
                 context.getString(R.string.controls_media_button_play),
-                context.getDrawable(R.drawable.ic_media_play_container)
+                MediaControlDrawables.getPlayBackground(context)
             )
         }
         PlaybackState.ACTION_PAUSE -> {
             MediaAction(
-                context.getDrawable(R.drawable.ic_media_pause),
+                MediaControlDrawables.getPauseIcon(context),
                 { controller.transportControls.pause() },
                 context.getString(R.string.controls_media_button_pause),
-                context.getDrawable(R.drawable.ic_media_pause_container)
+                MediaControlDrawables.getPauseBackground(context)
             )
         }
         PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
             MediaAction(
-                context.getDrawable(R.drawable.ic_media_prev),
+                MediaControlDrawables.getPrevIcon(context),
                 { controller.transportControls.skipToPrevious() },
                 context.getString(R.string.controls_media_button_prev),
                 null
@@ -177,7 +177,7 @@
         }
         PlaybackState.ACTION_SKIP_TO_NEXT -> {
             MediaAction(
-                context.getDrawable(R.drawable.ic_media_next),
+                MediaControlDrawables.getNextIcon(context),
                 { controller.transportControls.skipToNext() },
                 context.getString(R.string.controls_media_button_next),
                 null
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index adcfba7..415449f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -16,9 +16,8 @@
 
 package com.android.systemui.media.controls.domain.pipeline
 
+import android.annotation.MainThread
 import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
 import android.app.Notification
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
@@ -39,7 +38,6 @@
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
@@ -47,7 +45,6 @@
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.net.Uri
-import android.os.Handler
 import android.os.Parcelable
 import android.os.Process
 import android.os.UserHandle
@@ -63,6 +60,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -73,6 +71,7 @@
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.MediaControlDrawables
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
 import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.shared.model.MediaAction
@@ -90,7 +89,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
@@ -135,7 +134,7 @@
     @Background private val backgroundExecutor: Executor,
     @Main private val uiExecutor: Executor,
     @Main private val foregroundExecutor: DelayableExecutor,
-    @Main private val handler: Handler,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dumpManager: DumpManager,
@@ -150,6 +149,7 @@
     private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val mediaDataRepository: MediaDataRepository,
+    private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
 ) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -215,7 +215,7 @@
         threadFactory: ThreadFactory,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
-        @Main handler: Handler,
+        @Main mainDispatcher: CoroutineDispatcher,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
@@ -228,6 +228,7 @@
         smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
         mediaDataRepository: MediaDataRepository,
+        mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     ) : this(
         context,
         applicationScope,
@@ -237,7 +238,7 @@
         threadFactory.buildExecutorOnNewThread(TAG),
         uiExecutor,
         foregroundExecutor,
-        handler,
+        mainDispatcher,
         mediaControllerFactory,
         broadcastDispatcher,
         dumpManager,
@@ -252,6 +253,7 @@
         smartspaceManager,
         keyguardUpdateMonitor,
         mediaDataRepository,
+        mediaDataLoader,
     )
 
     private val appChangeReceiver =
@@ -271,7 +273,7 @@
         }
 
     override fun start() {
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             return
         }
 
@@ -434,16 +436,30 @@
             logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
             logger.logResumeMediaAdded(appUid, packageName, instanceId)
         }
-        backgroundExecutor.execute {
-            loadMediaDataInBgForResumption(
-                userId,
-                desc,
-                action,
-                token,
-                appName,
-                appIntent,
-                packageName
-            )
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            applicationScope.launch {
+                loadMediaDataForResumption(
+                    userId,
+                    desc,
+                    action,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
+        } else {
+            backgroundExecutor.execute {
+                loadMediaDataInBgForResumption(
+                    userId,
+                    desc,
+                    action,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
         }
     }
 
@@ -469,7 +485,13 @@
         oldKey: String?,
         isNewlyActiveEntry: Boolean = false,
     ) {
-        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            applicationScope.launch {
+                loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+            }
+        } else {
+            backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        }
     }
 
     /** Add a listener for internal events. */
@@ -644,6 +666,75 @@
         }
     }
 
+    private suspend fun loadMediaDataForResumption(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ) =
+        withContext(backgroundDispatcher) {
+            val lastActive = systemClock.elapsedRealtime()
+            val currentEntry = mediaDataRepository.mediaEntries.value[packageName]
+            val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+            val result =
+                mediaDataLoader
+                    .get()
+                    .loadMediaDataForResumption(
+                        userId,
+                        desc,
+                        resumeAction,
+                        currentEntry,
+                        token,
+                        appName,
+                        appIntent,
+                        packageName
+                    )
+            if (result == null || desc.title.isNullOrBlank()) {
+                Log.d(TAG, "No MediaData result for resumption")
+                mediaDataRepository.removeMediaEntry(packageName)
+                return@withContext
+            }
+
+            val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+            withContext(mainDispatcher) {
+                onMediaDataLoaded(
+                    packageName,
+                    null,
+                    MediaData(
+                        userId = userId,
+                        initialized = true,
+                        app = result.appName,
+                        appIcon = null,
+                        artist = result.artist,
+                        song = result.song,
+                        artwork = result.artworkIcon,
+                        actions = result.actionIcons,
+                        actionsToShowInCompact = result.actionsToShowInCompact,
+                        semanticActions = result.semanticActions,
+                        packageName = packageName,
+                        token = result.token,
+                        clickIntent = result.clickIntent,
+                        device = result.device,
+                        active = false,
+                        resumeAction = resumeAction,
+                        resumption = true,
+                        notificationKey = packageName,
+                        hasCheckedForResume = true,
+                        lastActive = lastActive,
+                        createdTimestampMillis = createdTimestampMillis,
+                        instanceId = instanceId,
+                        appUid = result.appUid,
+                        isExplicit = result.isExplicit,
+                        resumeProgress = result.resumeProgress,
+                    )
+                )
+            }
+        }
+
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun loadMediaDataInBgForResumption(
         userId: Int,
         desc: MediaDescription,
@@ -728,6 +819,82 @@
         }
     }
 
+    private suspend fun loadMediaDataWithLoader(
+        key: String,
+        sbn: StatusBarNotification,
+        oldKey: String?,
+        isNewlyActiveEntry: Boolean = false,
+    ) =
+        withContext(backgroundDispatcher) {
+            val lastActive = systemClock.elapsedRealtime()
+            val result = mediaDataLoader.get().loadMediaData(key, sbn)
+            if (result == null) {
+                Log.d(TAG, "No result from loadMediaData")
+                return@withContext
+            }
+
+            val currentEntry = mediaDataRepository.mediaEntries.value[key]
+            val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+            val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+            val resumeAction: Runnable? = currentEntry?.resumeAction
+            val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+            val active = currentEntry?.active ?: true
+
+            // We need to log the correct media added.
+            if (isNewlyActiveEntry) {
+                logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+                logger.logActiveMediaAdded(
+                    result.appUid,
+                    sbn.packageName,
+                    instanceId,
+                    result.playbackLocation
+                )
+            } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+                logger.logPlaybackLocationChange(
+                    result.appUid,
+                    sbn.packageName,
+                    instanceId,
+                    result.playbackLocation
+                )
+            }
+
+            withContext(mainDispatcher) {
+                onMediaDataLoaded(
+                    key,
+                    oldKey,
+                    MediaData(
+                        userId = sbn.normalizedUserId,
+                        initialized = true,
+                        app = result.appName,
+                        appIcon = result.appIcon,
+                        artist = result.artist,
+                        song = result.song,
+                        artwork = result.artworkIcon,
+                        actions = result.actionIcons,
+                        actionsToShowInCompact = result.actionsToShowInCompact,
+                        semanticActions = result.semanticActions,
+                        packageName = sbn.packageName,
+                        token = result.token,
+                        clickIntent = result.clickIntent,
+                        device = result.device,
+                        active = active,
+                        resumeAction = resumeAction,
+                        playbackLocation = result.playbackLocation,
+                        notificationKey = key,
+                        hasCheckedForResume = hasCheckedForResume,
+                        isPlaying = result.isPlaying,
+                        isClearable = !sbn.isOngoing,
+                        lastActive = lastActive,
+                        createdTimestampMillis = createdTimestampMillis,
+                        instanceId = instanceId,
+                        appUid = result.appUid,
+                        isExplicit = result.isExplicit,
+                    )
+                )
+            }
+        }
+
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     fun loadMediaDataInBg(
         key: String,
         sbn: StatusBarNotification,
@@ -841,7 +1008,7 @@
         var actionsToShowCollapsed: List<Int> = emptyList()
         val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
         if (semanticActions == null) {
-            val actions = createActionsFromNotification(sbn)
+            val actions = createActionsFromNotification(context, activityStarter, sbn)
             actionIcons = actions.first
             actionsToShowCollapsed = actions.second
         }
@@ -924,6 +1091,7 @@
         }
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
         try {
             return context.packageManager.getApplicationInfo(packageName, 0)
@@ -933,6 +1101,7 @@
         return null
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
         val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
         if (name != null) {
@@ -946,78 +1115,6 @@
         }
     }
 
-    /** Generate action buttons based on notification actions */
-    private fun createActionsFromNotification(
-        sbn: StatusBarNotification
-    ): Pair<List<MediaAction>, List<Int>> {
-        val notif = sbn.notification
-        val actionIcons: MutableList<MediaAction> = ArrayList()
-        val actions = notif.actions
-        var actionsToShowCollapsed =
-            notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
-                ?: mutableListOf()
-        if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(
-                TAG,
-                "Too many compact actions for ${sbn.key}," +
-                    "limiting to first $MAX_COMPACT_ACTIONS"
-            )
-            actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
-        }
-
-        if (actions != null) {
-            for ((index, action) in actions.withIndex()) {
-                if (index == MAX_NOTIFICATION_ACTIONS) {
-                    Log.w(
-                        TAG,
-                        "Too many notification actions for ${sbn.key}," +
-                            " limiting to first $MAX_NOTIFICATION_ACTIONS"
-                    )
-                    break
-                }
-                if (action.getIcon() == null) {
-                    if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
-                    actionsToShowCollapsed.remove(index)
-                    continue
-                }
-                val runnable =
-                    if (action.actionIntent != null) {
-                        Runnable {
-                            if (action.actionIntent.isActivity) {
-                                activityStarter.startPendingIntentDismissingKeyguard(
-                                    action.actionIntent
-                                )
-                            } else if (action.isAuthenticationRequired()) {
-                                activityStarter.dismissKeyguardThenExecute(
-                                    {
-                                        var result = sendPendingIntent(action.actionIntent)
-                                        result
-                                    },
-                                    {},
-                                    true
-                                )
-                            } else {
-                                sendPendingIntent(action.actionIntent)
-                            }
-                        }
-                    } else {
-                        null
-                    }
-                val mediaActionIcon =
-                    if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
-                            Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
-                        } else {
-                            action.getIcon()
-                        }
-                        .setTint(themeText)
-                        .loadDrawable(context)
-                val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
-                actionIcons.add(mediaAction)
-            }
-        }
-        return Pair(actionIcons, actionsToShowCollapsed)
-    }
-
     /**
      * Generates action button info for this media session based on the PlaybackState
      *
@@ -1034,175 +1131,14 @@
         controller: MediaController,
         user: UserHandle
     ): MediaButton? {
-        val state = controller.playbackState
-        if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+        if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
             return null
         }
-
-        // First, check for standard actions
-        val playOrPause =
-            if (isConnectingState(state.state)) {
-                // Spinner needs to be animating to render anything. Start it here.
-                val drawable =
-                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
-                (drawable as Animatable).start()
-                MediaAction(
-                    drawable,
-                    null, // no action to perform when clicked
-                    context.getString(R.string.controls_media_button_connecting),
-                    context.getDrawable(R.drawable.ic_media_connecting_container),
-                    // Specify a rebind id to prevent the spinner from restarting on later binds.
-                    com.android.internal.R.drawable.progress_small_material
-                )
-            } else if (isPlayingState(state.state)) {
-                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
-            } else {
-                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
-            }
-        val prevButton =
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
-        val nextButton =
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
-        // Then, create a way to build any custom actions that will be needed
-        val customActions =
-            state.customActions
-                .asSequence()
-                .filterNotNull()
-                .map { getCustomAction(packageName, controller, it) }
-                .iterator()
-        fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
-        // Finally, assign the remaining button slots: play/pause A B C D
-        // A = previous, else custom action (if not reserved)
-        // B = next, else custom action (if not reserved)
-        // C and D are always custom actions
-        val reservePrev =
-            controller.extras?.getBoolean(
-                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
-            ) == true
-        val reserveNext =
-            controller.extras?.getBoolean(
-                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
-            ) == true
-
-        val prevOrCustom =
-            if (prevButton != null) {
-                prevButton
-            } else if (!reservePrev) {
-                nextCustomAction()
-            } else {
-                null
-            }
-
-        val nextOrCustom =
-            if (nextButton != null) {
-                nextButton
-            } else if (!reserveNext) {
-                nextCustomAction()
-            } else {
-                null
-            }
-
-        return MediaButton(
-            playOrPause,
-            nextOrCustom,
-            prevOrCustom,
-            nextCustomAction(),
-            nextCustomAction(),
-            reserveNext,
-            reservePrev
-        )
-    }
-
-    /**
-     * Create a [MediaAction] for a given action and media session
-     *
-     * @param controller MediaController for the session
-     * @param stateActions The actions included with the session's [PlaybackState]
-     * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
-     * ```
-     *      [PlaybackState.ACTION_PLAY]
-     *      [PlaybackState.ACTION_PAUSE]
-     *      [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
-     *      [PlaybackState.ACTION_SKIP_TO_NEXT]
-     * @return
-     * ```
-     *
-     * A [MediaAction] with correct values set, or null if the state doesn't support it
-     */
-    private fun getStandardAction(
-        controller: MediaController,
-        stateActions: Long,
-        @PlaybackState.Actions action: Long
-    ): MediaAction? {
-        if (!includesAction(stateActions, action)) {
-            return null
-        }
-
-        return when (action) {
-            PlaybackState.ACTION_PLAY -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_play),
-                    { controller.transportControls.play() },
-                    context.getString(R.string.controls_media_button_play),
-                    context.getDrawable(R.drawable.ic_media_play_container)
-                )
-            }
-            PlaybackState.ACTION_PAUSE -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_pause),
-                    { controller.transportControls.pause() },
-                    context.getString(R.string.controls_media_button_pause),
-                    context.getDrawable(R.drawable.ic_media_pause_container)
-                )
-            }
-            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_prev),
-                    { controller.transportControls.skipToPrevious() },
-                    context.getString(R.string.controls_media_button_prev),
-                    null
-                )
-            }
-            PlaybackState.ACTION_SKIP_TO_NEXT -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_next),
-                    { controller.transportControls.skipToNext() },
-                    context.getString(R.string.controls_media_button_next),
-                    null
-                )
-            }
-            else -> null
-        }
-    }
-
-    /** Check whether the actions from a [PlaybackState] include a specific action */
-    private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
-        if (
-            (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
-                (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
-        ) {
-            return true
-        }
-        return (stateActions and action != 0L)
-    }
-
-    /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
-    private fun getCustomAction(
-        packageName: String,
-        controller: MediaController,
-        customAction: PlaybackState.CustomAction
-    ): MediaAction {
-        return MediaAction(
-            Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
-            { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
-            customAction.name,
-            null
-        )
+        return createActionsFromState(context, packageName, controller)
     }
 
     /** Load a bitmap from the various Art metadata URIs */
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
         for (uri in ART_URIS) {
             val uriString = metadata.getString(uri)
@@ -1217,21 +1153,6 @@
         return null
     }
 
-    private fun sendPendingIntent(intent: PendingIntent): Boolean {
-        return try {
-            val options = BroadcastOptions.makeBasic()
-            options.setInteractive(true)
-            options.setPendingIntentBackgroundActivityStartMode(
-                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-            )
-            intent.send(options.toBundle())
-            true
-        } catch (e: PendingIntent.CanceledException) {
-            Log.d(TAG, "Intent canceled", e)
-            false
-        }
-    }
-
     /** Returns a bitmap if the user can access the given URI, else null */
     private fun loadBitmapFromUriForUser(
         uri: Uri,
@@ -1308,10 +1229,11 @@
                 .loadDrawable(context),
             action,
             context.getString(R.string.controls_media_resume),
-            context.getDrawable(R.drawable.ic_media_play_container)
+            MediaControlDrawables.getPlayBackground(context)
         )
     }
 
+    @MainThread
     fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
         traceSection("MediaDataProcessor#onMediaDataLoaded") {
             Assert.isMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
new file mode 100644
index 0000000..f886166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLogger.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.media.session.MediaController
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.MediaDeviceLog
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import javax.inject.Inject
+
+/** A [LogBuffer] for media device changes */
+class MediaDeviceLogger @Inject constructor(@MediaDeviceLog private val buffer: LogBuffer) {
+
+    fun logBroadcastEvent(event: String, reason: Int, broadcastId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event
+                int1 = reason
+                int2 = broadcastId
+            },
+            { "$str1, reason = $int1, broadcastId = $int2" }
+        )
+    }
+
+    fun logBroadcastEvent(event: String, reason: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event
+                int1 = reason
+            },
+            { "$str1, reason = $int1" }
+        )
+    }
+
+    fun logBroadcastMetadataChanged(broadcastId: Int, metadata: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = broadcastId
+                str1 = metadata
+            },
+            { "onBroadcastMetadataChanged, broadcastId = $int1, metadata = $str1" }
+        )
+    }
+
+    fun logNewDeviceName(name: String?) {
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = name }, { "New device name $str1" })
+    }
+
+    fun logLocalDevice(sassDevice: MediaDeviceData?, connectedDevice: MediaDeviceData?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = sassDevice?.name?.toString()
+                str2 = connectedDevice?.name?.toString()
+            },
+            { "Local device: $str1 or $str2" }
+        )
+    }
+
+    fun logRemoteDevice(routingSessionName: CharSequence?, connectedDevice: MediaDeviceData?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = routingSessionName?.toString()
+                str2 = connectedDevice?.name?.toString()
+            },
+            { "Remote device: $str1 or $str2 or unknown" }
+        )
+    }
+
+    fun logDeviceName(
+        device: MediaDevice?,
+        controller: MediaController?,
+        routingSessionName: CharSequence?,
+        selectedRouteName: CharSequence?
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = "device $device, controller: $controller"
+                str2 = routingSessionName?.toString()
+                str3 = selectedRouteName?.toString()
+            },
+            { "$str1, routingSession $str2 or selected route $str3" }
+        )
+    }
+
+    companion object {
+        private const val TAG = "MediaDeviceLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index eab0d48..49b53c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -39,6 +39,7 @@
 import com.android.settingslib.media.flags.Flags
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.shared.MediaControlDrawables
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.MediaDeviceData
 import com.android.systemui.media.controls.util.LocalMediaManagerFactory
@@ -70,6 +71,7 @@
     private val localBluetoothManager: Lazy<LocalBluetoothManager?>,
     @Main private val fgExecutor: Executor,
     @Background private val bgExecutor: Executor,
+    private val logger: MediaDeviceLogger,
 ) : MediaDataManager.Listener {
 
     private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -142,6 +144,7 @@
     interface Listener {
         /** Called when the route has changed for a given notification. */
         fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
+
         /** Called when the notification was removed. */
         fun onKeyRemoved(key: String, userInitiated: Boolean)
     }
@@ -159,6 +162,7 @@
 
         val token
             get() = controller?.sessionToken
+
         private var started = false
         private var playbackType = PLAYBACK_TYPE_UNKNOWN
         private var playbackVolumeControlId: String? = null
@@ -170,6 +174,7 @@
                     fgExecutor.execute { processDevice(key, oldKey, value) }
                 }
             }
+
         // A device that is not yet connected but is expected to connect imminently. Because it's
         // expected to connect imminently, it should be displayed as the current device.
         private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@@ -277,59 +282,38 @@
         }
 
         override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastStarted", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastStartFailed(reason: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
-            }
+            logger.logBroadcastEvent("onBroadcastStartFailed", reason)
         }
 
         override fun onBroadcastMetadataChanged(
             broadcastId: Int,
             metadata: BluetoothLeBroadcastMetadata
         ) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
-                        "metadata = $metadata"
-                )
-            }
+            logger.logBroadcastMetadataChanged(broadcastId, metadata.toString())
             updateCurrent()
         }
 
         override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastStopped", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastStopFailed(reason: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
-            }
+            logger.logBroadcastEvent("onBroadcastStopFailed", reason)
         }
 
         override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
-            }
+            logger.logBroadcastEvent("onBroadcastUpdated", reason, broadcastId)
             updateCurrent()
         }
 
         override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
-                )
-            }
+            logger.logBroadcastEvent("onBroadcastUpdateFailed", reason, broadcastId)
         }
 
         override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
@@ -354,12 +338,12 @@
 
                     activeDevice =
                         routingSession?.let {
-                            val icon = if (it.selectedRoutes.size > 1) {
-                                context.getDrawable(
-                                        com.android.settingslib.R.drawable.ic_media_group_device)
-                            } else {
-                                connectedDevice?.icon // Single route. We don't change the icon.
-                            }
+                            val icon =
+                                if (it.selectedRoutes.size > 1) {
+                                    MediaControlDrawables.getGroupDevice(context)
+                                } else {
+                                    connectedDevice?.icon // Single route. We don't change the icon.
+                                }
                             // For a remote session, always use the current device from
                             // LocalMediaManager. Override with routing session information if
                             // available:
@@ -367,20 +351,26 @@
                             //   - Icon: To show the group icon if there's more than one selected
                             //           route.
                             connectedDevice?.copy(
-                                    name = it.name ?: connectedDevice.name,
-                                    icon = icon)
-                        } ?: MediaDeviceData(
-                            enabled = false,
-                            icon = context.getDrawable(R.drawable.ic_media_home_devices),
-                            name = context.getString(R.string.media_seamless_other_device),
-                            showBroadcastButton = false
-                        )
+                                name = it.name ?: connectedDevice.name,
+                                icon = icon
+                            )
+                        }
+                            ?: MediaDeviceData(
+                                enabled = false,
+                                icon = MediaControlDrawables.getHomeDevices(context),
+                                name = context.getString(R.string.media_seamless_other_device),
+                                showBroadcastButton = false
+                            )
+                    logger.logRemoteDevice(routingSession?.name, connectedDevice)
                 } else {
                     // Prefer SASS if available when playback is local.
-                    activeDevice = getSassDevice() ?: connectedDevice
+                    val sassDevice = getSassDevice()
+                    activeDevice = sassDevice ?: connectedDevice
+                    logger.logLocalDevice(sassDevice, connectedDevice)
                 }
 
                 current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA
+                logger.logNewDeviceName(current?.name?.toString())
             } else {
                 val aboutToConnect = aboutToConnectDeviceOverride
                 if (
@@ -401,9 +391,7 @@
                 val enabled = device != null && (controller == null || routingSession != null)
 
                 val name = getDeviceName(device, routingSession)
-                if (DEBUG) {
-                    Log.d(TAG, "new device name $name")
-                }
+                logger.logNewDeviceName(name)
                 current =
                     MediaDeviceData(
                         enabled,
@@ -434,10 +422,7 @@
             return if (enableLeAudioSharing()) {
                 MediaDeviceData(
                     enabled = false,
-                    icon =
-                        context.getDrawable(
-                            com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
-                        ),
+                    icon = MediaControlDrawables.getLeAudioSharing(context),
                     name = context.getString(R.string.audio_sharing_description),
                     intent = null,
                     showBroadcastButton = false
@@ -445,13 +430,14 @@
             } else {
                 MediaDeviceData(
                     enabled = true,
-                    icon = context.getDrawable(R.drawable.settings_input_antenna),
+                    icon = MediaControlDrawables.getAntenna(context),
                     name = broadcastDescription,
                     intent = null,
                     showBroadcastButton = true
                 )
             }
         }
+
         /** Return a display name for the current device / route, or null if not possible */
         private fun getDeviceName(
             device: MediaDevice?,
@@ -459,14 +445,12 @@
         ): String? {
             val selectedRoutes = routingSession?.let { mr2manager.get().getSelectedRoutes(it) }
 
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "device is $device, controller $controller," +
-                        " routingSession ${routingSession?.name}" +
-                        " or ${selectedRoutes?.firstOrNull()?.name}"
-                )
-            }
+            logger.logDeviceName(
+                device,
+                controller,
+                routingSession?.name,
+                selectedRoutes?.firstOrNull()?.name
+            )
 
             if (controller == null) {
                 // In resume state, we don't have a controller - just use the device name
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 9d7160c..270ab72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -105,7 +105,7 @@
     val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
 
     override fun start() {
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
index 28ee668..c78220e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaControlDrawables.kt
@@ -45,128 +45,139 @@
     private var solid: Drawable? = null
 
     fun getProgress(context: Context): Drawable? {
-        return progress
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+        }
+        return progress?.mutate()
             ?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 progress = it
             }
     }
 
     fun getConnecting(context: Context): Drawable? {
-        return connecting
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_connecting_container)
+        }
+        return connecting?.mutate()
             ?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 connecting = it
             }
     }
 
     fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?
+        }
         return playIcon?.let {
             it.reset()
-            it
+            it.mutate() as AnimatedVectorDrawable
         }
             ?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 playIcon = it
             }
     }
 
     fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_play_container)
+                as AnimatedVectorDrawable?
+        }
         return playBackground?.let {
             it.reset()
-            it
+            it.mutate() as AnimatedVectorDrawable
         }
             ?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
-                .also {
-                    if (!mediaControlsDrawablesReuse()) return@also
-                    playBackground = it
-                }
+                .also { playBackground = it }
     }
 
     fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?
+        }
         return pauseIcon?.let {
             it.reset()
-            it
+            it.mutate() as AnimatedVectorDrawable
         }
             ?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 pauseIcon = it
             }
     }
 
     fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_pause_container)
+                as AnimatedVectorDrawable?
+        }
         return pauseBackground?.let {
             it.reset()
-            it
+            it.mutate() as AnimatedVectorDrawable
         }
             ?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
-                .also {
-                    if (!mediaControlsDrawablesReuse()) return@also
-                    pauseBackground = it
-                }
+                .also { pauseBackground = it }
     }
 
     fun getNextIcon(context: Context): Drawable? {
-        return nextIcon
-            ?: context.getDrawable(R.drawable.ic_media_next).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                nextIcon = it
-            }
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_next)
+        }
+        return nextIcon ?: context.getDrawable(R.drawable.ic_media_next).also { nextIcon = it }
     }
 
     fun getPrevIcon(context: Context): Drawable? {
-        return prevIcon
-            ?: context.getDrawable(R.drawable.ic_media_prev).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                prevIcon = it
-            }
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_prev)
+        }
+        return prevIcon ?: context.getDrawable(R.drawable.ic_media_prev).also { prevIcon = it }
     }
 
     fun getLeAudioSharing(context: Context): Drawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
+        }
         return leAudioSharing
             ?: context.getDrawable(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 leAudioSharing = it
             }
     }
 
     fun getAntenna(context: Context): Drawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.settings_input_antenna)
+        }
         return antenna
-            ?: context.getDrawable(R.drawable.settings_input_antenna).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                antenna = it
-            }
+            ?: context.getDrawable(R.drawable.settings_input_antenna).also { antenna = it }
     }
 
     fun getGroupDevice(context: Context): Drawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device)
+        }
         return groupDevice
             ?: context.getDrawable(com.android.settingslib.R.drawable.ic_media_group_device).also {
-                if (!mediaControlsDrawablesReuse()) return@also
                 groupDevice = it
             }
     }
 
     fun getHomeDevices(context: Context): Drawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.ic_media_home_devices)
+        }
         return homeDevices
-            ?: context.getDrawable(R.drawable.ic_media_home_devices).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                homeDevices = it
-            }
+            ?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = it }
     }
 
     fun getOutline(context: Context): Drawable? {
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.qs_media_outline_button)
+        }
         return outline
-            ?: context.getDrawable(R.drawable.qs_media_outline_button).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                outline = it
-            }
+            ?: context.getDrawable(R.drawable.qs_media_outline_button).also { outline = it }
     }
 
     fun getSolid(context: Context): Drawable? {
-        return solid
-            ?: context.getDrawable(R.drawable.qs_media_solid_button).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                solid = it
-            }
+        if (!mediaControlsDrawablesReuse()) {
+            return context.getDrawable(R.drawable.qs_media_solid_button)
+        }
+        return solid ?: context.getDrawable(R.drawable.qs_media_solid_button).also { solid = it }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index fb2bbde..bf9ef8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -45,10 +45,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -75,7 +75,6 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.system.SysUiStatsLog
@@ -103,13 +102,16 @@
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -121,6 +123,7 @@
  * Class that is responsible for keeping the view carousel up to date. This also handles changes in
  * state and applies them to the media carousel like the expansion.
  */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class MediaCarouselController
 @Inject
@@ -149,7 +152,7 @@
     private val secureSettings: SecureSettings,
     private val mediaCarouselViewModel: MediaCarouselViewModel,
     private val mediaViewControllerFactory: Provider<MediaViewController>,
-    private val sceneInteractor: SceneInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
 ) : Dumpable {
     /** The current width of the carousel */
     var currentCarouselWidth: Int = 0
@@ -164,6 +167,9 @@
     /** Is the player currently visible (at the end of the transformation */
     private var playersVisible: Boolean = false
 
+    /** Are we currently disabling pagination only allowing one media session to show */
+    private var currentlyDisablePagination: Boolean = false
+
     /**
      * The desired location where we'll be at the end of the transformation. Usually this matches
      * the end location, except when we're still waiting on a state update call.
@@ -220,7 +226,7 @@
     private val animationScaleObserver: ContentObserver =
         object : ContentObserver(executor, 0) {
             override fun onChange(selfChange: Boolean) {
-                if (!mediaFlags.isSceneContainerEnabled()) {
+                if (!SceneContainerFlag.isEnabled) {
                     MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
                 } else {
                     controllerById.values.forEach { it.updateAnimatorDurationScale() }
@@ -327,6 +333,11 @@
     private val controllerById = mutableMapOf<String, MediaViewController>()
     private val commonViewModels = mutableListOf<MediaCommonViewModel>()
 
+    private val isOnGone =
+        keyguardTransitionInteractor
+            .isFinishedIn(Scenes.Gone, GONE)
+            .stateIn(applicationScope, SharingStarted.Eagerly, true)
+
     init {
         dumpManager.registerDumpable(TAG, this)
         mediaFrame = inflateMediaCarousel()
@@ -350,7 +361,7 @@
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
         configurationController.addCallback(configListener)
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             setUpListeners()
         } else {
             val visualStabilityCallback = OnReorderingAllowedListener {
@@ -391,7 +402,7 @@
                 listenForAnyStateToGoneKeyguardTransition(this)
                 listenForAnyStateToLockscreenTransition(this)
 
-                if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
+                if (!SceneContainerFlag.isEnabled) return@repeatOnLifecycle
                 listenForMediaItemsChanges(this)
             }
         }
@@ -733,7 +744,7 @@
         when (commonViewModel) {
             is MediaCommonViewModel.MediaControl -> {
                 val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
-                if (mediaFlags.isSceneContainerEnabled()) {
+                if (SceneContainerFlag.isEnabled) {
                     viewController.widthInSceneContainerPx = widthInSceneContainerPx
                     viewController.heightInSceneContainerPx = heightInSceneContainerPx
                 }
@@ -904,9 +915,13 @@
 
     /** Return true if the carousel should be hidden because lockscreen is currently visible */
     fun isLockedAndHidden(): Boolean {
-        val keyguardState = keyguardTransitionInteractor.getFinishedState()
-        return !allowMediaPlayerOnLockScreen &&
-            KeyguardState.lockscreenVisibleInState(keyguardState)
+        val isOnLockscreen =
+            if (SceneContainerFlag.isEnabled) {
+                !deviceEntryInteractor.isDeviceEntered.value
+            } else {
+                !isOnGone.value
+            }
+        return !allowMediaPlayerOnLockScreen && isOnLockscreen
     }
 
     private fun reorderAllPlayers(
@@ -965,7 +980,7 @@
                     .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
             if (existingPlayer == null) {
                 val newPlayer = mediaControlPanelFactory.get()
-                if (mediaFlags.isSceneContainerEnabled()) {
+                if (SceneContainerFlag.isEnabled) {
                     newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
                     newPlayer.mediaViewController.heightInSceneContainerPx =
                         heightInSceneContainerPx
@@ -1140,7 +1155,7 @@
     }
 
     private fun updatePlayers(recreateMedia: Boolean) {
-        if (mediaFlags.isSceneContainerEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             updateMediaPlayers(recreateMedia)
             return
         }
@@ -1240,7 +1255,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            if (!mediaFlags.isSceneContainerEnabled()) {
+            if (!SceneContainerFlag.isEnabled) {
                 for (mediaPlayer in MediaPlayerData.players()) {
                     updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
                 }
@@ -1300,7 +1315,7 @@
 
     /** Update listening to seekbar. */
     private fun updateSeekbarListening(visibleToUser: Boolean) {
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             for (player in MediaPlayerData.players()) {
                 player.setListening(visibleToUser && currentlyExpanded)
             }
@@ -1313,7 +1328,7 @@
     private fun updateCarouselDimensions() {
         var width = 0
         var height = 0
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             for (mediaPlayer in MediaPlayerData.players()) {
                 val controller = mediaPlayer.mediaViewController
                 // When transitioning the view to gone, the view gets smaller, but the translation
@@ -1347,14 +1362,20 @@
         val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
         val startShowsActive =
             hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
+        val startDisablePagination = hostStates[currentStartLocation]?.disablePagination ?: false
+        val endDisablePagination = hostStates[currentEndLocation]?.disablePagination ?: false
+
         if (
             currentlyShowingOnlyActive != endShowsActive ||
+                currentlyDisablePagination != endDisablePagination ||
                 ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
-                    startShowsActive != endShowsActive)
+                    (startShowsActive != endShowsActive ||
+                        startDisablePagination != endDisablePagination))
         ) {
             // Whenever we're transitioning from between differing states or the endstate differs
             // we reset the translation
             currentlyShowingOnlyActive = endShowsActive
+            currentlyDisablePagination = endDisablePagination
             mediaCarouselScrollHandler.resetTranslation(animate = true)
         }
     }
@@ -1405,7 +1426,7 @@
                         !mediaManager.hasActiveMediaOrRecommendation() &&
                         desiredHostState.showsOnlyActiveMedia
 
-                if (!mediaFlags.isSceneContainerEnabled()) {
+                if (!SceneContainerFlag.isEnabled) {
                     for (mediaPlayer in MediaPlayerData.players()) {
                         if (animate) {
                             mediaPlayer.mediaViewController.animatePendingStateChange(
@@ -1445,7 +1466,7 @@
         }
 
     fun closeGuts(immediate: Boolean = true) {
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             MediaPlayerData.players().forEach { it.closeGuts(immediate) }
         } else {
             controllerById.values.forEach { it.closeGuts(immediate) }
@@ -1596,7 +1617,7 @@
 
     @VisibleForTesting
     fun onSwipeToDismiss() {
-        if (mediaFlags.isSceneContainerEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             mediaCarouselViewModel.onSwipeToDismiss(currentEndLocation)
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index addb014..87610cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -111,7 +111,6 @@
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
 import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.controls.util.MediaFlags;
 import com.android.systemui.media.controls.util.MediaUiEventLogger;
 import com.android.systemui.media.controls.util.SmallHash;
 import com.android.systemui.media.dialog.MediaOutputDialogManager;
@@ -120,6 +119,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -209,7 +209,6 @@
     static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
 
     private final SeekBarViewModel mSeekBarViewModel;
-    private final MediaFlags mMediaFlags;
     private final CommunalSceneInteractor mCommunalSceneInteractor;
     private SeekBarObserver mSeekBarObserver;
     protected final Executor mBackgroundExecutor;
@@ -323,8 +322,7 @@
             CommunalSceneInteractor communalSceneInteractor,
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDialogController broadcastDialogController,
-            GlobalSettings globalSettings,
-            MediaFlags mediaFlags
+            GlobalSettings globalSettings
     ) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
@@ -343,7 +341,6 @@
         mActivityIntentHelper = activityIntentHelper;
         mLockscreenUserManager = lockscreenUserManager;
         mBroadcastDialogController = broadcastDialogController;
-        mMediaFlags = mediaFlags;
         mCommunalSceneInteractor = communalSceneInteractor;
 
         mSeekBarViewModel.setLogSeek(() -> {
@@ -641,7 +638,7 @@
         // State refresh interferes with the translation animation, only run it if it's not running.
         if (!mMetadataAnimationHandler.isRunning()) {
             // Don't refresh in scene framework, because it will calculate with invalid layout sizes
-            if (!mMediaFlags.isSceneContainerEnabled()) {
+            if (!SceneContainerFlag.isEnabled()) {
                 mMediaViewController.refreshState();
             }
         }
@@ -909,7 +906,7 @@
         // Capture width & height from views in foreground for artwork scaling in background
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
-        if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+        if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
             // TODO(b/312714128): ensure we have a valid size before setting background
             width = mMediaViewController.getWidthInSceneContainerPx();
             height = mMediaViewController.getHeightInSceneContainerPx();
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 091b886..a9d2a54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -46,10 +46,10 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.StatusBarState
@@ -119,7 +119,6 @@
     @Application private val coroutineScope: CoroutineScope,
     private val splitShadeStateController: SplitShadeStateController,
     private val logger: MediaViewLogger,
-    private val mediaFlags: MediaFlags,
 ) {
 
     /** Track the media player setting status on lock screen. */
@@ -1111,7 +1110,7 @@
 
     private fun updateHostAttachment() =
         traceSection("MediaHierarchyManager#updateHostAttachment") {
-            if (mediaFlags.isSceneContainerEnabled()) {
+            if (SceneContainerFlag.isEnabled) {
                 // No need to manage transition states - just update the desired location directly
                 logger.logMediaHostAttachment(desiredLocation)
                 mediaCarouselController.onDesiredLocationChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 584908f..e57de09 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -46,8 +46,8 @@
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.surfaceeffects.PaintDrawCallback
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
@@ -82,7 +82,6 @@
     private val logger: MediaViewLogger,
     private val seekBarViewModel: SeekBarViewModel,
     @Main private val mainExecutor: DelayableExecutor,
-    private val mediaFlags: MediaFlags,
     private val globalSettings: GlobalSettings,
 ) {
 
@@ -125,7 +124,7 @@
         set(value) {
             if (field != value) {
                 field = value
-                if (!mediaFlags.isSceneContainerEnabled()) return
+                if (!SceneContainerFlag.isEnabled) return
                 locationChangeListener(value)
             }
         }
@@ -212,7 +211,7 @@
     private val scrubbingChangeListener =
         object : SeekBarViewModel.ScrubbingChangeListener {
             override fun onScrubbingChanged(scrubbing: Boolean) {
-                if (!mediaFlags.isSceneContainerEnabled()) return
+                if (!SceneContainerFlag.isEnabled) return
                 if (isScrubbing == scrubbing) return
                 isScrubbing = scrubbing
                 updateDisplayForScrubbingChange()
@@ -222,7 +221,7 @@
     private val enabledChangeListener =
         object : SeekBarViewModel.EnabledChangeListener {
             override fun onEnabledChanged(enabled: Boolean) {
-                if (!mediaFlags.isSceneContainerEnabled()) return
+                if (!SceneContainerFlag.isEnabled) return
                 if (isSeekBarEnabled == enabled) return
                 isSeekBarEnabled = enabled
                 MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
@@ -238,7 +237,7 @@
      * @param listening True when player should be active. Otherwise, false.
      */
     fun setListening(listening: Boolean) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         seekBarViewModel.listening = listening
     }
 
@@ -272,7 +271,7 @@
                             )
                         )
                     }
-                    if (mediaFlags.isSceneContainerEnabled()) {
+                    if (SceneContainerFlag.isEnabled) {
                         if (
                             this@MediaViewController::recsConfigurationChangeListener.isInitialized
                         ) {
@@ -344,7 +343,7 @@
      * Notify this controller that the view has been removed and all listeners should be destroyed
      */
     fun onDestroy() {
-        if (mediaFlags.isSceneContainerEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             if (this::seekBarObserver.isInitialized) {
                 seekBarViewModel.progress.removeObserver(seekBarObserver)
             }
@@ -565,7 +564,7 @@
         state: MediaHostState?,
         isGutsAnimation: Boolean = false
     ): TransitionViewState? {
-        if (mediaFlags.isSceneContainerEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             return obtainSceneContainerViewState()
         }
 
@@ -667,7 +666,7 @@
         }
 
     fun attachPlayer(mediaViewHolder: MediaViewHolder) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         this.mediaViewHolder = mediaViewHolder
 
         // Setting up seek bar.
@@ -741,7 +740,7 @@
     }
 
     fun updateAnimatorDurationScale() {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         if (this::seekBarObserver.isInitialized) {
             seekBarObserver.animationEnabled =
                 globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
@@ -801,7 +800,7 @@
     }
 
     fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         this.recommendationViewHolder = recommendationViewHolder
 
         attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
@@ -810,13 +809,13 @@
     }
 
     fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         seekBarViewModel.logSeek = onSeek
         onBindSeekBar(seekBarViewModel)
     }
 
     fun setUpTurbulenceNoise() {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         mediaViewHolder!!.let {
             if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
                 turbulenceNoiseAnimationConfig =
@@ -1049,7 +1048,7 @@
      */
     private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
         val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
-        if (mediaFlags.isSceneContainerEnabled()) {
+        if (SceneContainerFlag.isEnabled) {
             return obtainSceneContainerViewState()
         }
 
@@ -1080,7 +1079,7 @@
     /** Clear all existing measurements and refresh the state to match the view. */
     fun refreshState() =
         traceSection("MediaViewController#refreshState") {
-            if (mediaFlags.isSceneContainerEnabled()) {
+            if (SceneContainerFlag.isEnabled) {
                 // We don't need to recreate measurements for scene container, since it's a known
                 // size. Just get the view state and update the layout controller
                 obtainSceneContainerViewState()?.let {
@@ -1169,13 +1168,13 @@
     }
 
     fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         isPrevButtonAvailable = isAvailable
         prevNotVisibleValue = notVisibleValue
     }
 
     fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
-        if (!mediaFlags.isSceneContainerEnabled()) return
+        if (!SceneContainerFlag.isEnabled) return
         isNextButtonAvailable = isAvailable
         nextNotVisibleValue = notVisibleValue
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 91050c8..09a6181 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -44,6 +44,7 @@
     lateinit var hostView: UniqueObjectHostView
     var location: Int = -1
         private set
+
     private var visibleChangedListeners: ArraySet<(Boolean) -> Unit> = ArraySet()
 
     private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
@@ -287,6 +288,15 @@
                 changedListener?.invoke()
             }
 
+        override var disablePagination: Boolean = false
+            set(value) {
+                if (field == value) {
+                    return
+                }
+                field = value
+                changedListener?.invoke()
+            }
+
         private var lastDisappearHash = disappearParameters.hashCode()
 
         /** A listener for all changes. This won't be copied over when invoking [copy] */
@@ -303,6 +313,7 @@
             mediaHostState.visible = visible
             mediaHostState.disappearParameters = disappearParameters.deepCopy()
             mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded
+            mediaHostState.disablePagination = disablePagination
             return mediaHostState
         }
 
@@ -331,6 +342,9 @@
             if (!disappearParameters.equals(other.disappearParameters)) {
                 return false
             }
+            if (disablePagination != other.disablePagination) {
+                return false
+            }
             return true
         }
 
@@ -342,6 +356,7 @@
             result = 31 * result + showsOnlyActiveMedia.hashCode()
             result = 31 * result + if (visible) 1 else 2
             result = 31 * result + disappearParameters.hashCode()
+            result = 31 * result + disablePagination.hashCode()
             return result
         }
     }
@@ -400,6 +415,12 @@
      */
     var disappearParameters: DisappearParameters
 
+    /**
+     * Whether pagination should be disabled for this host, meaning that when there are multiple
+     * media sessions, only the first one will appear.
+     */
+    var disablePagination: Boolean
+
     /** Get a copy of this view state, deepcopying all appropriate members */
     fun copy(): MediaHostState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 64820e0..f460134 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
+import com.android.systemui.media.controls.shared.MediaControlDrawables
 import com.android.systemui.media.controls.shared.model.MediaAction
 import com.android.systemui.media.controls.shared.model.MediaButton
 import com.android.systemui.media.controls.shared.model.MediaControlModel
@@ -284,9 +285,9 @@
             },
             cancelTextBackground =
                 if (model.isDismissible) {
-                    applicationContext.getDrawable(R.drawable.qs_media_outline_button)
+                    MediaControlDrawables.getOutline(applicationContext)
                 } else {
-                    applicationContext.getDrawable(R.drawable.qs_media_solid_button)
+                    MediaControlDrawables.getSolid(applicationContext)
                 },
             onSettingsClicked = {
                 logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 21c3111..a65243d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import javax.inject.Inject
 
 @SysUISingleton
@@ -49,7 +48,4 @@
 
     /** Check whether we allow remote media to generate resume controls */
     fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
-
-    /** Check whether to use scene framework */
-    fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 42f66cc..7d2a1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags.glanceableHubBackGesture
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -107,10 +106,7 @@
                     {
                         it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion
                     },
-                SYSUI_STATE_COMMUNAL_HUB_SHOWING to
-                    {
-                        glanceableHubBackGesture() && it.scene == Scenes.Communal
-                    }
+                SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.scene == Scenes.Communal }
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index ac878c2..173a964 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -19,19 +19,24 @@
 import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
+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.SOFTWARE;
 import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON;
 import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -60,10 +65,11 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
@@ -74,6 +80,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.rotation.RotationPolicyUtil;
 import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -107,6 +114,7 @@
         AccessibilityManager.AccessibilityServicesStateChangeListener,
         AccessibilityButtonModeObserver.ModeChangedListener,
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
+        AccessibilityGestureTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
         Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
     private static final String TAG = NavBarHelper.class.getSimpleName();
@@ -122,6 +130,7 @@
     private final SystemActions mSystemActions;
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
+    private final AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver;
     private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>();
     private final Context mContext;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -188,6 +197,7 @@
     public NavBarHelper(Context context, AccessibilityManager accessibilityManager,
             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
             AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
+            AccessibilityGestureTargetsObserver accessibilityGestureTargetsObserver,
             SystemActions systemActions,
             OverviewProxyService overviewProxyService,
             Lazy<AssistManager> assistManagerLazy,
@@ -220,6 +230,7 @@
         mSystemActions = systemActions;
         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
+        mAccessibilityGestureTargetsObserver = accessibilityGestureTargetsObserver;
         mWm = wm;
         mDefaultDisplayId = displayTracker.getDefaultDisplayId();
         mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
@@ -249,6 +260,7 @@
         mAccessibilityManager.addAccessibilityServicesStateChangeListener(this);
         mAccessibilityButtonModeObserver.addListener(this);
         mAccessibilityButtonTargetsObserver.addListener(this);
+        mAccessibilityGestureTargetsObserver.addListener(this);
 
         // Setup assistant listener
         mContentResolver.registerContentObserver(
@@ -291,6 +303,7 @@
         mAccessibilityManager.removeAccessibilityServicesStateChangeListener(this);
         mAccessibilityButtonModeObserver.removeListener(this);
         mAccessibilityButtonTargetsObserver.removeListener(this);
+        mAccessibilityGestureTargetsObserver.removeListener(this);
 
         // Clean up assistant listeners
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
@@ -380,43 +393,50 @@
     }
 
     @Override
+    public void onAccessibilityGestureTargetsChanged(String targets) {
+        updateA11yState();
+    }
+
+    @Override
     public void onConfigChanged(Configuration newConfig) {
         mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
     }
 
+    private int getNumOfA11yShortcutTargetsForNavSystem() {
+        final int buttonMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+        final int shortcutType;
+        if (!android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            shortcutType = buttonMode
+                    != ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU ? SOFTWARE : DEFAULT;
+            // If accessibility button is floating menu mode, there are no clickable targets.
+        } else {
+            if (mNavBarMode == NAV_BAR_MODE_GESTURAL) {
+                shortcutType = GESTURE;
+            } else {
+                shortcutType = buttonMode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR
+                        ? SOFTWARE : DEFAULT;
+            }
+        }
+        return mAccessibilityManager.getAccessibilityShortcutTargets(shortcutType).size();
+    }
+
     /**
      * Updates the current accessibility button state. The accessibility button state is only
      * used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
      * {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
      */
-    private void updateA11yState() {
+    @VisibleForTesting
+    void updateA11yState() {
         final long prevState = mA11yButtonState;
         final boolean clickable;
         final boolean longClickable;
-        if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
-                == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
-            // If accessibility button is floating menu mode, click and long click state should be
-            // disabled.
-            clickable = false;
-            longClickable = false;
-            mA11yButtonState = 0;
-        } else {
-            // AccessibilityManagerService resolves services for the current user since the local
-            // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS
-            // permission
-            final List<String> a11yButtonTargets =
-                    mAccessibilityManager.getAccessibilityShortcutTargets(
-                            ShortcutConstants.UserShortcutType.SOFTWARE);
-            final int requestingServices = a11yButtonTargets.size();
-
-            clickable = requestingServices >= 1;
-
-            // `longClickable` is used to determine whether to pop up the accessibility chooser
-            // dialog or not, and it’s also only for multiple services.
-            longClickable = requestingServices >= 2;
-            mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
-                    | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
-        }
+        int clickableServices = getNumOfA11yShortcutTargetsForNavSystem();
+        clickable = clickableServices >= 1;
+        // `longClickable` is used to determine whether to pop up the accessibility chooser
+        // dialog or not, and it’s also only for multiple services.
+        longClickable = clickableServices >= 2;
+        mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+                | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
 
         // Update the system actions if the state has changed
         if (prevState != mA11yButtonState) {
@@ -482,9 +502,11 @@
                 Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0,
                 mUserTracker.getUserId()) != 0;
 
+        boolean supportsSwipeGesture = QuickStepContract.isGesturalMode(mNavBarMode)
+                || (QuickStepContract.isLegacyMode(mNavBarMode) && Flags.threeButtonCornerSwipe());
         mAssistantAvailable = assistantAvailableForUser
                 && mAssistantTouchGestureEnabled
-                && QuickStepContract.isGesturalMode(mNavBarMode);
+                && supportsSwipeGesture;
         dispatchAssistantEventUpdate(mAssistantAvailable, mLongPressHomeEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0f82e02..6bd880d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -78,6 +78,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -474,9 +475,14 @@
                 } else {
                     String[] gestureBlockingActivities = resources.getStringArray(resId);
                     for (String gestureBlockingActivity : gestureBlockingActivities) {
-                        mGestureInteractor.addGestureBlockedActivity(
-                                ComponentName.unflattenFromString(gestureBlockingActivity),
-                                GestureInteractor.Scope.Local);
+                        final ComponentName component =
+                                ComponentName.unflattenFromString(gestureBlockingActivity);
+
+                        if (component != null) {
+                            mGestureInteractor.addGestureBlockedMatcher(
+                                    new TaskMatcher.TopActivityComponent(component),
+                                    GestureInteractor.Scope.Local);
+                        }
                     }
                 }
             } catch (NameNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
index 8f35343..c1f238a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
@@ -16,10 +16,9 @@
 
 package com.android.systemui.navigationbar.gestural.data.respository
 
-import android.content.ComponentName
-import android.util.ArraySet
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.navigationbar.gestural.domain.TaskMatcher
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -28,36 +27,43 @@
 
 /** A repository for storing gesture related information */
 interface GestureRepository {
-    /** A {@link StateFlow} tracking activities currently blocked from gestures. */
-    val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+    /** A {@link StateFlow} tracking matchers that can block gestures. */
+    val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>>
 
-    /** Adds an activity to be blocked from gestures. */
-    suspend fun addGestureBlockedActivity(activity: ComponentName)
+    /** Adds a matcher to determine whether a gesture should be blocked. */
+    suspend fun addGestureBlockedMatcher(matcher: TaskMatcher)
 
-    /** Removes an activity from being blocked from gestures. */
-    suspend fun removeGestureBlockedActivity(activity: ComponentName)
+    /** Removes a matcher from blocking from gestures. */
+    suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher)
 }
 
 @SysUISingleton
 class GestureRepositoryImpl
 @Inject
 constructor(@Main private val mainDispatcher: CoroutineDispatcher) : GestureRepository {
-    private val _gestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(ArraySet())
+    private val _gestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(emptySet())
 
-    override val gestureBlockedActivities: StateFlow<Set<ComponentName>>
-        get() = _gestureBlockedActivities
+    override val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>>
+        get() = _gestureBlockedMatchers
 
-    override suspend fun addGestureBlockedActivity(activity: ComponentName) =
+    override suspend fun addGestureBlockedMatcher(matcher: TaskMatcher) =
         withContext(mainDispatcher) {
-            _gestureBlockedActivities.emit(
-                _gestureBlockedActivities.value.toMutableSet().apply { add(activity) }
-            )
+            val existingMatchers = _gestureBlockedMatchers.value
+            if (existingMatchers.contains(matcher)) {
+                return@withContext
+            }
+
+            _gestureBlockedMatchers.value = existingMatchers.toMutableSet().apply { add(matcher) }
         }
 
-    override suspend fun removeGestureBlockedActivity(activity: ComponentName) =
+    override suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher) =
         withContext(mainDispatcher) {
-            _gestureBlockedActivities.emit(
-                _gestureBlockedActivities.value.toMutableSet().apply { remove(activity) }
-            )
+            val existingMatchers = _gestureBlockedMatchers.value
+            if (!existingMatchers.contains(matcher)) {
+                return@withContext
+            }
+
+            _gestureBlockedMatchers.value =
+                existingMatchers.toMutableSet().apply { remove(matcher) }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
index 6182878..96386e5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.navigationbar.gestural.domain
 
-import android.content.ComponentName
 import com.android.app.tracing.coroutines.flow.flowOn
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +24,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.shared.system.TaskStackChangeListener
 import com.android.systemui.shared.system.TaskStackChangeListeners
-import com.android.systemui.util.kotlin.combine
 import com.android.systemui.util.kotlin.emitOnStart
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
@@ -60,7 +58,7 @@
         Global
     }
 
-    private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf())
+    private val _localGestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(setOf())
 
     private val _topActivity =
         conflatedCallbackFlow {
@@ -79,53 +77,47 @@
             .mapLatest { getTopActivity() }
             .distinctUntilChanged()
 
-    private suspend fun getTopActivity(): ComponentName? =
+    private suspend fun getTopActivity(): TaskInfo? =
         withContext(backgroundCoroutineContext) {
-            val runningTask = activityManagerWrapper.runningTask
-            runningTask?.topActivity
+            activityManagerWrapper.runningTask?.let { TaskInfo(it.topActivity, it.activityType) }
         }
 
     val topActivityBlocked =
         combine(
             _topActivity,
-            gestureRepository.gestureBlockedActivities,
-            _localGestureBlockedActivities.asStateFlow()
-        ) { activity, global, local ->
-            activity != null && (global + local).contains(activity)
+            gestureRepository.gestureBlockedMatchers,
+            _localGestureBlockedMatchers.asStateFlow()
+        ) { runningTask, global, local ->
+            runningTask != null && (global + local).any { it.matches(runningTask) }
         }
 
-    /**
-     * Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link
-     * Activity}.
-     */
-    fun addGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+    /** Adds an [TaskMatcher] to decide whether gestures should be blocked. */
+    fun addGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
         scope.launch {
             when (gestureScope) {
                 Scope.Local -> {
-                    _localGestureBlockedActivities.emit(
-                        _localGestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+                    _localGestureBlockedMatchers.emit(
+                        _localGestureBlockedMatchers.value.toMutableSet().apply { add(matcher) }
                     )
                 }
                 Scope.Global -> {
-                    gestureRepository.addGestureBlockedActivity(activity)
+                    gestureRepository.addGestureBlockedMatcher(matcher)
                 }
             }
         }
     }
 
-    /** Removes an {@link Activity} from being blocked from gestures. */
-    fun removeGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+    /** Removes a gesture from deciding whether gestures should be blocked */
+    fun removeGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
         scope.launch {
             when (gestureScope) {
                 Scope.Local -> {
-                    _localGestureBlockedActivities.emit(
-                        _localGestureBlockedActivities.value.toMutableSet().apply {
-                            remove(activity)
-                        }
+                    _localGestureBlockedMatchers.emit(
+                        _localGestureBlockedMatchers.value.toMutableSet().apply { remove(matcher) }
                     )
                 }
                 Scope.Global -> {
-                    gestureRepository.removeGestureBlockedActivity(activity)
+                    gestureRepository.removeGestureBlockedMatcher(matcher)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt
new file mode 100644
index 0000000..d62b2c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.navigationbar.gestural.domain
+
+import android.content.ComponentName
+
+/**
+ * A simple data class for capturing details around a task. Implements equality to ensure changes
+ * can be identified between emitted values.
+ */
+data class TaskInfo(val topActivity: ComponentName?, val topActivityType: Int) {
+    override fun equals(other: Any?): Boolean {
+        return other is TaskInfo &&
+            other.topActivityType == topActivityType &&
+            other.topActivity == topActivity
+    }
+}
+
+/**
+ * [TaskMatcher] provides a way to identify a task based on particular attributes, such as the top
+ * activity type or component name.
+ */
+sealed interface TaskMatcher {
+    fun matches(info: TaskInfo): Boolean
+
+    class TopActivityType(private val type: Int) : TaskMatcher {
+        override fun matches(info: TaskInfo): Boolean {
+            return info.topActivity != null && info.topActivityType == type
+        }
+    }
+
+    class TopActivityComponent(private val component: ComponentName) : TaskMatcher {
+        override fun matches(info: TaskInfo): Boolean {
+            return component == info.topActivity
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 3613c11..c39ff55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.runtime.Composable
@@ -45,6 +46,10 @@
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.round
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -265,7 +270,7 @@
     }
 
     override fun setCollapseExpandAction(action: Runnable?) {
-        // Nothing to do yet. But this should be wired to a11y
+        viewModel.collapseExpandAccessibilityAction = action
     }
 
     override fun getHeightDiff(): Int {
@@ -399,27 +404,40 @@
             onDispose { qqsVisible.value = false }
         }
         Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
-            QuickQuickSettings(
-                viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
-                modifier =
-                    Modifier.onGloballyPositioned { coordinates ->
-                            val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
-                            val (width, height) = coordinates.size
-                            qqsPositionOnRoot.set(
-                                leftFromRoot,
-                                topFromRoot,
-                                leftFromRoot + width,
-                                topFromRoot + height
-                            )
-                        }
-                        .layout { measurable, constraints ->
-                            val placeable = measurable.measure(constraints)
-                            qqsHeight.value = placeable.height
+            Box(modifier = Modifier.fillMaxWidth()) {
+                val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
+                if (qsEnabled) {
+                    QuickQuickSettings(
+                        viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+                        modifier =
+                            Modifier.onGloballyPositioned { coordinates ->
+                                    val (leftFromRoot, topFromRoot) =
+                                        coordinates.positionInRoot().round()
+                                    val (width, height) = coordinates.size
+                                    qqsPositionOnRoot.set(
+                                        leftFromRoot,
+                                        topFromRoot,
+                                        leftFromRoot + width,
+                                        topFromRoot + height
+                                    )
+                                }
+                                .layout { measurable, constraints ->
+                                    val placeable = measurable.measure(constraints)
+                                    qqsHeight.value = placeable.height
 
-                            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
-                        }
-                        .padding(top = { qqsPadding })
-            )
+                                    layout(placeable.width, placeable.height) {
+                                        placeable.place(0, 0)
+                                    }
+                                }
+                                .padding(top = { qqsPadding })
+                                .collapseExpandSemanticAction(
+                                    stringResource(
+                                        id = R.string.accessibility_quick_settings_expand
+                                    )
+                                )
+                    )
+                }
+            }
             Spacer(modifier = Modifier.weight(1f))
         }
     }
@@ -428,22 +446,46 @@
     private fun QuickSettingsElement() {
         val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
         val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
-        Column {
-            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
-                Column {
-                    Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
-                    ShadeBody(viewModel = viewModel.containerViewModel)
+        Column(
+            modifier =
+                Modifier.collapseExpandSemanticAction(
+                    stringResource(id = R.string.accessibility_quick_settings_collapse)
+                )
+        ) {
+            val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle()
+            if (qsEnabled) {
+                Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+                    Column {
+                        Spacer(
+                            modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }
+                        )
+                        ShadeBody(viewModel = viewModel.containerViewModel)
+                    }
+                }
+                QuickSettingsTheme {
+                    FooterActions(
+                        viewModel = viewModel.footerActionsViewModel,
+                        qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+                        modifier = Modifier.sysuiResTag("qs_footer_actions")
+                    )
                 }
             }
-            QuickSettingsTheme {
-                FooterActions(
-                    viewModel = viewModel.footerActionsViewModel,
-                    qsVisibilityLifecycleOwner = this@QSFragmentCompose,
-                    modifier = Modifier.sysuiResTag("qs_footer_actions")
-                )
-            }
         }
     }
+
+    private fun Modifier.collapseExpandSemanticAction(label: String): Modifier {
+        return viewModel.collapseExpandAccessibilityAction?.let {
+            semantics {
+                customActions =
+                    listOf(
+                        CustomAccessibilityAction(label) {
+                            it.run()
+                            true
+                        }
+                    )
+            }
+        } ?: this
+    }
 }
 
 private fun View.setBackPressedDispatcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 4b1312c..16133f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -132,13 +132,17 @@
             _stackScrollerOverscrolling.value = value
         }
 
-    private val qsDisabled =
+    /**
+     * Whether QS is enabled by policy. This is normally true, except when it's disabled by some
+     * policy. See [DisableFlagsRepository].
+     */
+    val qsEnabled =
         disableFlagsRepository.disableFlags
-            .map { !it.isQuickSettingsEnabled() }
+            .map { it.isQuickSettingsEnabled() }
             .stateIn(
                 lifecycleScope,
                 SharingStarted.WhileSubscribed(),
-                !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+                disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
             )
 
     private val _showCollapsedOnKeyguard = MutableStateFlow(false)
@@ -218,6 +222,12 @@
             }
             .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
 
+    /**
+     * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
+     * determining the correct action based on the expansion state.
+     */
+    var collapseExpandAccessibilityAction: Runnable? = null
+
     @AssistedFactory
     interface Factory {
         fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 6dc101a..b0d4fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -42,6 +42,8 @@
 import javax.inject.Named
 import javax.inject.Provider
 import kotlin.math.max
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -145,10 +147,11 @@
             )
         }
 
+        @OptIn(ExperimentalCoroutinesApi::class)
         fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
             val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
             if (lifecycleCoroutineScope.isActive) {
-                lifecycleCoroutineScope.launch {
+                lifecycleCoroutineScope.launch(start = CoroutineStart.ATOMIC) {
                     try {
                         awaitCancellation()
                     } finally {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 0b9cd96..9a2315b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -158,18 +158,20 @@
     return item.span != 1 && offset.x > itemCenter.x
 }
 
+@Composable
 fun Modifier.dragAndDropTileSource(
     sizedTile: SizedTile<EditTileViewModel>,
     onTap: (TileSpec) -> Unit,
     onDoubleTap: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState
 ): Modifier {
+    val state by rememberUpdatedState(dragAndDropState)
     return dragAndDropSource {
         detectTapGestures(
             onTap = { onTap(sizedTile.tile.tileSpec) },
             onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
             onLongPress = {
-                dragAndDropState.onStarted(sizedTile)
+                state.onStarted(sizedTile)
 
                 // The tilespec from the ClipData transferred isn't actually needed as we're moving
                 // a tile within the same application. We're using a custom MIME type to limit the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 8887f58..9abc494 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -71,6 +71,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Base quick-settings tile, extend this to create a new tile.
@@ -350,6 +351,7 @@
 
     public void userSwitch(int newUserId) {
         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
+        postStale();
     }
 
     public void destroy() {
@@ -667,6 +669,18 @@
         public String toString() {
             return "DrawableIcon";
         }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            // No need to compare equality of the mInvisibleDrawable as that's generated from
+            // mDrawable's constant state.
+            return other instanceof DrawableIcon && ((DrawableIcon) other).mDrawable == mDrawable;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDrawable);
+        }
     }
 
     public static class DrawableIconWithRes extends DrawableIcon {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 5f5b265..3f18fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -57,7 +57,7 @@
                     isActivated = modes.isNotEmpty(),
                     icon =
                         if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons())
-                            zenModeInteractor.getActiveModeIcon(context, modes)
+                            zenModeInteractor.getActiveModeIcon(modes)
                         else null,
                     activeModes = modes.map { it.name }
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
index af55f5a..2bb5dc66 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
@@ -31,7 +31,6 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -78,7 +77,7 @@
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
index 55b8f5f..93bf73f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -44,7 +43,7 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     val mediaCarouselInteractor: MediaCarouselInteractor,
-) : SysUiViewModel() {
+) {
 
     val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
index d2967b8..9956a46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 
 /**
@@ -62,7 +61,7 @@
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
index abfca4b..924a939 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
-import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -35,7 +34,7 @@
 constructor(
     val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
     val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SysUiViewModel() {
+) {
 
     @AssistedFactory
     interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ecf816b..fe5cbb1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,7 +26,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.Flags.glanceableHubBackGesture;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -86,10 +85,10 @@
 import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
@@ -231,7 +230,7 @@
                         // If scene framework is enabled, set the scene container window to
                         // visible and let the touch "slip" into that window.
                         if (SceneContainerFlag.isEnabled()) {
-                            mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
+                            mSceneInteractor.get().onRemoteUserInputStarted("launcher swipe");
                         } else {
                             mShadeViewControllerLazy.get().startInputFocusTransfer();
                         }
@@ -267,7 +266,7 @@
                 if (SceneContainerFlag.isEnabled()) {
                     int action = event.getActionMasked();
                     if (action == ACTION_DOWN) {
-                        mSceneInteractor.get().onRemoteUserInteractionStarted(
+                        mSceneInteractor.get().onRemoteUserInputStarted(
                                 "trackpad swipe");
                     } else if (action == ACTION_UP) {
                         mSceneInteractor.get().changeScene(
@@ -837,8 +836,7 @@
                 .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
                 .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
                 .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
-                .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING,
-                        glanceableHubBackGesture() && communalShowing)
+                .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING, communalShowing)
                 .commitUpdate(mContext.getDisplayId());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt b/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
index efb9375..4c730a0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Overlay
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ElementsIntoSet
@@ -29,4 +30,10 @@
     fun emptySceneSet(): Set<Scene> {
         return emptySet()
     }
+
+    @Provides
+    @ElementsIntoSet
+    fun emptyOverlaySet(): Set<Overlay> {
+        return emptySet()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 9a7eef8..16ed59f4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -53,6 +53,7 @@
                     Scenes.Bouncer,
                 ),
             initialSceneKey = Scenes.Lockscreen,
+            overlayKeys = emptyList(),
             navigationDistances =
                 mapOf(
                     Scenes.Gone to 0,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 3e2c630..d60f05e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -18,7 +18,9 @@
 
 package com.android.systemui.scene.data.repository
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
@@ -43,11 +45,27 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    private val config: SceneContainerConfig,
+    config: SceneContainerConfig,
     private val dataSource: SceneDataSource,
 ) {
+    /**
+     * The keys of all scenes and overlays in the container.
+     *
+     * They will be sorted in z-order such that the last one is the one that should be rendered on
+     * top of all previous ones.
+     */
+    val allContentKeys: List<ContentKey> = config.sceneKeys + config.overlayKeys
+
     val currentScene: StateFlow<SceneKey> = dataSource.currentScene
 
+    /**
+     * The current set of overlays to be shown (may be empty).
+     *
+     * Note that during a transition between overlays, a different set of overlays may be rendered -
+     * but only the ones in this set are considered the current overlays.
+     */
+    val currentOverlays: StateFlow<Set<OverlayKey>> = dataSource.currentOverlays
+
     private val _isVisible = MutableStateFlow(true)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
@@ -56,7 +74,10 @@
      *
      * For more information see the logic in `SceneInteractor` that mutates this.
      */
-    val isRemoteUserInteractionOngoing = MutableStateFlow(false)
+    val isRemoteUserInputOngoing = MutableStateFlow(false)
+
+    /** Whether there's ongoing user input on the scene container Composable hierarchy */
+    val isSceneContainerUserInputOngoing = MutableStateFlow(false)
 
     private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
     private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
@@ -69,16 +90,6 @@
                 initialValue = defaultTransitionState,
             )
 
-    /**
-     * Returns the keys to all scenes in the container.
-     *
-     * The scenes will be sorted in z-order such that the last one is the one that should be
-     * rendered on top of all previous ones.
-     */
-    fun allSceneKeys(): List<SceneKey> {
-        return config.sceneKeys
-    }
-
     fun changeScene(
         toScene: SceneKey,
         transitionKey: TransitionKey? = null,
@@ -97,6 +108,48 @@
         )
     }
 
+    /**
+     * Request to show [overlay] so that it animates in from [currentScene] and ends up being
+     * visible on screen.
+     *
+     * After this returns, this overlay will be included in [currentOverlays]. This does nothing if
+     * [overlay] is already shown.
+     */
+    fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
+        dataSource.showOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
+     * Request to hide [overlay] so that it animates out to [currentScene] and ends up *not* being
+     * visible on screen.
+     *
+     * After this returns, this overlay will not be included in [currentOverlays]. This does nothing
+     * if [overlay] is already hidden.
+     */
+    fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey? = null) {
+        dataSource.hideOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
+     * Replace [from] by [to] so that [from] ends up not being visible on screen and [to] ends up
+     * being visible.
+     *
+     * This throws if [from] is not currently shown or if [to] is already shown.
+     */
+    fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey? = null) {
+        dataSource.replaceOverlay(
+            from = from,
+            to = to,
+            transitionKey = transitionKey,
+        )
+    }
+
     /** Sets whether the container is visible. */
     fun setVisible(isVisible: Boolean) {
         _isVisible.value = isVisible
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index ea61bd3..04620d6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -118,7 +118,7 @@
         get() =
             when (this) {
                 is ObservableTransitionState.Idle -> currentScene.canBeOccluded
-                is ObservableTransitionState.Transition.ChangeCurrentScene ->
+                is ObservableTransitionState.Transition.ChangeScene ->
                     fromScene.canBeOccluded && toScene.canBeOccluded
                 is ObservableTransitionState.Transition.ReplaceOverlay,
                 is ObservableTransitionState.Transition.ShowOrHideOverlay ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 1b9c346..a2142b6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
@@ -51,6 +53,7 @@
  * other feature modules should depend on and call into this class when their parts of the
  * application state change.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SceneInteractor
 @Inject
@@ -76,6 +79,14 @@
     private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
 
     /**
+     * The keys of all scenes and overlays in the container.
+     *
+     * They will be sorted in z-order such that the last one is the one that should be rendered on
+     * top of all previous ones.
+     */
+    val allContentKeys: List<ContentKey> = repository.allContentKeys
+
+    /**
      * The current scene.
      *
      * Note that during a transition between scenes, more than one scene might be rendered but only
@@ -84,6 +95,14 @@
     val currentScene: StateFlow<SceneKey> = repository.currentScene
 
     /**
+     * The current set of overlays to be shown (may be empty).
+     *
+     * Note that during a transition between overlays, a different set of overlays may be rendered -
+     * but only the ones in this set are considered the current overlays.
+     */
+    val currentOverlays: StateFlow<Set<OverlayKey>> = repository.currentOverlays
+
+    /**
      * The current state of the transition.
      *
      * Consumers should use this state to know:
@@ -112,7 +131,7 @@
             .map { state ->
                 when (state) {
                     is ObservableTransitionState.Idle -> null
-                    is ObservableTransitionState.Transition.ChangeCurrentScene -> state.toScene
+                    is ObservableTransitionState.Transition.ChangeScene -> state.toScene
                     is ObservableTransitionState.Transition.ShowOrHideOverlay,
                     is ObservableTransitionState.Transition.ReplaceOverlay ->
                         TODO("b/359173565: Handle overlay transitions")
@@ -148,11 +167,11 @@
     val isVisible: StateFlow<Boolean> =
         combine(
                 repository.isVisible,
-                repository.isRemoteUserInteractionOngoing,
+                repository.isRemoteUserInputOngoing,
             ) { isVisible, isRemoteUserInteractionOngoing ->
                 isVisibleInternal(
                     raw = isVisible,
-                    isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing,
+                    isRemoteUserInputOngoing = isRemoteUserInteractionOngoing,
                 )
             }
             .stateIn(
@@ -162,8 +181,13 @@
             )
 
     /** Whether there's an ongoing remotely-initiated user interaction. */
-    val isRemoteUserInteractionOngoing: StateFlow<Boolean> =
-        repository.isRemoteUserInteractionOngoing
+    val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInputOngoing
+
+    /**
+     * Whether there's an ongoing user interaction started in the scene container Compose hierarchy.
+     */
+    val isSceneContainerUserInputOngoing: StateFlow<Boolean> =
+        repository.isSceneContainerUserInputOngoing
 
     /**
      * The amount of transition into or out of the given [scene].
@@ -187,16 +211,6 @@
         }
     }
 
-    /**
-     * Returns the keys of all scenes in the container.
-     *
-     * The scenes will be sorted in z-order such that the last one is the one that should be
-     * rendered on top of all previous ones.
-     */
-    fun allSceneKeys(): List<SceneKey> {
-        return repository.allSceneKeys()
-    }
-
     fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
         onSceneAboutToChangeListener.add(processor)
     }
@@ -279,12 +293,111 @@
     }
 
     /**
+     * Request to show [overlay] so that it animates in from [currentScene] and ends up being
+     * visible on screen.
+     *
+     * After this returns, this overlay will be included in [currentOverlays]. This does nothing if
+     * [overlay] is already shown.
+     *
+     * @param overlay The overlay to be shown
+     * @param loggingReason The reason why the transition is requested, for logging purposes
+     * @param transitionKey The transition key for this animated transition
+     */
+    @JvmOverloads
+    fun showOverlay(
+        overlay: OverlayKey,
+        loggingReason: String,
+        transitionKey: TransitionKey? = null,
+    ) {
+        if (!validateOverlayChange(to = overlay, loggingReason = loggingReason)) {
+            return
+        }
+
+        logger.logOverlayChangeRequested(
+            to = overlay,
+            reason = loggingReason,
+        )
+
+        repository.showOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
+     * Request to hide [overlay] so that it animates out to [currentScene] and ends up *not* being
+     * visible on screen.
+     *
+     * After this returns, this overlay will not be included in [currentOverlays]. This does nothing
+     * if [overlay] is already hidden.
+     *
+     * @param overlay The overlay to be hidden
+     * @param loggingReason The reason why the transition is requested, for logging purposes
+     * @param transitionKey The transition key for this animated transition
+     */
+    @JvmOverloads
+    fun hideOverlay(
+        overlay: OverlayKey,
+        loggingReason: String,
+        transitionKey: TransitionKey? = null,
+    ) {
+        if (!validateOverlayChange(from = overlay, loggingReason = loggingReason)) {
+            return
+        }
+
+        logger.logOverlayChangeRequested(
+            from = overlay,
+            reason = loggingReason,
+        )
+
+        repository.hideOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
+     * Replace [from] by [to] so that [from] ends up not being visible on screen and [to] ends up
+     * being visible.
+     *
+     * This throws if [from] is not currently shown or if [to] is already shown.
+     *
+     * @param from The overlay to be hidden, if any
+     * @param to The overlay to be shown, if any
+     * @param loggingReason The reason why the transition is requested, for logging purposes
+     * @param transitionKey The transition key for this animated transition
+     */
+    @JvmOverloads
+    fun replaceOverlay(
+        from: OverlayKey,
+        to: OverlayKey,
+        loggingReason: String,
+        transitionKey: TransitionKey? = null,
+    ) {
+        if (!validateOverlayChange(from = from, to = to, loggingReason = loggingReason)) {
+            return
+        }
+
+        logger.logOverlayChangeRequested(
+            from = from,
+            to = to,
+            reason = loggingReason,
+        )
+
+        repository.replaceOverlay(
+            from = from,
+            to = to,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
      * Sets the visibility of the container.
      *
      * Please do not call this from outside of the scene framework. If you are trying to force the
      * visibility to visible or invisible, prefer making changes to the existing caller of this
      * method or to upstream state used to calculate [isVisible]; for an example of the latter,
-     * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished].
+     * please see [onRemoteUserInputStarted] and [onUserInputFinished].
      */
     fun setVisible(isVisible: Boolean, loggingReason: String) {
         val wasVisible = repository.isVisible.value
@@ -301,6 +414,16 @@
     }
 
     /**
+     * Notifies that a scene container user interaction has begun.
+     *
+     * This is a user interaction that originates within the Composable hierarchy of the scene
+     * container.
+     */
+    fun onSceneContainerUserInputStarted() {
+        repository.isSceneContainerUserInputOngoing.value = true
+    }
+
+    /**
      * Notifies that a remote user interaction has begun.
      *
      * This is a user interaction that originates outside of the UI of the scene container and
@@ -311,18 +434,19 @@
      * then rerouted by window manager to System UI. While the user interaction definitely continues
      * within the System UI process and code, it also originates remotely.
      */
-    fun onRemoteUserInteractionStarted(loggingReason: String) {
-        logger.logRemoteUserInteractionStarted(loggingReason)
-        repository.isRemoteUserInteractionOngoing.value = true
+    fun onRemoteUserInputStarted(loggingReason: String) {
+        logger.logRemoteUserInputStarted(loggingReason)
+        repository.isRemoteUserInputOngoing.value = true
     }
 
     /**
      * Notifies that the current user interaction (internally or remotely started, see
-     * [onRemoteUserInteractionStarted]) has finished.
+     * [onSceneContainerUserInputStarted] and [onRemoteUserInputStarted]) has finished.
      */
-    fun onUserInteractionFinished() {
-        logger.logUserInteractionFinished()
-        repository.isRemoteUserInteractionOngoing.value = false
+    fun onUserInputFinished() {
+        logger.logUserInputFinished()
+        repository.isSceneContainerUserInputOngoing.value = false
+        repository.isRemoteUserInputOngoing.value = false
     }
 
     /**
@@ -351,9 +475,9 @@
 
     private fun isVisibleInternal(
         raw: Boolean = repository.isVisible.value,
-        isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value,
+        isRemoteUserInputOngoing: Boolean = repository.isRemoteUserInputOngoing.value,
     ): Boolean {
-        return raw || isRemoteUserInteractionOngoing
+        return raw || isRemoteUserInputOngoing
     }
 
     /**
@@ -372,7 +496,7 @@
         to: SceneKey,
         loggingReason: String,
     ): Boolean {
-        if (!repository.allSceneKeys().contains(to)) {
+        if (to !in repository.allContentKeys) {
             return false
         }
 
@@ -393,6 +517,34 @@
         return from != to
     }
 
+    /**
+     * Validates that the given overlay change is allowed.
+     *
+     * Will throw a runtime exception for illegal states.
+     *
+     * @param from The overlay to be hidden, if any
+     * @param to The overlay to be shown, if any
+     * @param loggingReason The reason why the transition is requested, for logging purposes
+     * @return `true` if the scene change is valid; `false` if it shouldn't happen
+     */
+    private fun validateOverlayChange(
+        from: OverlayKey? = null,
+        to: OverlayKey? = null,
+        loggingReason: String,
+    ): Boolean {
+        check(from != null || to != null) {
+            "No overlay key provided for requested change." +
+                " Current transition state is ${transitionState.value}." +
+                " Logging reason for overlay change was: $loggingReason"
+        }
+
+        val isFromValid = (from == null) || (from in currentOverlays.value)
+        val isToValid =
+            (to == null) || (to !in currentOverlays.value && to in repository.allContentKeys)
+
+        return isFromValid && isToValid && from != to
+    }
+
     /** Returns a flow indicating if the currently visible scene can be resolved from [family]. */
     fun isCurrentSceneInFamily(family: SceneKey): Flow<Boolean> =
         currentScene.map { currentScene -> isSceneInFamily(currentScene, family) }
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 cc46216..7eb48d6 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
@@ -479,7 +479,7 @@
                     switchToScene(
                         targetSceneKey = Scenes.Lockscreen,
                         loggingReason = "device is starting to sleep",
-                        sceneState = keyguardTransitionInteractor.asleepKeyguardState.value,
+                        sceneState = keyguardInteractor.asleepKeyguardState.value,
                     )
                 } else {
                     val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index ec743ba..d1629c7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -112,7 +112,7 @@
                 // It
                 // happens only when unlocking or when dismissing a dismissible lockscreen.
                 val isTransitioningAwayFromKeyguard =
-                    transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
+                    transitionState is ObservableTransitionState.Transition.ChangeScene &&
                         transitionState.fromScene.isKeyguard() &&
                         transitionState.toScene == Scenes.Gone
 
@@ -120,7 +120,7 @@
                 val isCurrentSceneShade = currentScene.isShade()
                 // This is true when moving into one of the shade scenes when a non-shade scene.
                 val isTransitioningToShade =
-                    transitionState is ObservableTransitionState.Transition.ChangeCurrentScene &&
+                    transitionState is ObservableTransitionState.Transition.ChangeScene &&
                         !transitionState.fromScene.isShade() &&
                         transitionState.toScene.isShade()
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
index 893f030..d741368 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
@@ -166,7 +166,7 @@
                     StatusBarManager.DISABLE_NONE,
                     disableToken,
                     applicationContext.packageName,
-                    selectedUserInteractor.getSelectedUserId(true),
+                    selectedUserInteractor.getSelectedUserId(),
                 )
             } catch (e: RemoteException) {
                 Log.d(TAG, "Failed to clear flags", e)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 6c63c97..751448f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
 
 /** Helper for reading or using the scene container flag state. */
@@ -43,7 +43,7 @@
                 KeyguardBottomAreaRefactor.isEnabled &&
                 KeyguardWmStateRefactor.isEnabled &&
                 MigrateClocksToBlueprint.isEnabled &&
-                NotificationsHeadsUpRefactor.isEnabled &&
+                NotificationThrottleHun.isEnabled &&
                 PredictiveBackSysUiFlag.isEnabled &&
                 DeviceEntryUdfpsRefactor.isEnabled
 
@@ -59,7 +59,7 @@
             KeyguardBottomAreaRefactor.token,
             KeyguardWmStateRefactor.token,
             MigrateClocksToBlueprint.token,
-            NotificationsHeadsUpRefactor.token,
+            NotificationThrottleHun.token,
             PredictiveBackSysUiFlag.token,
             DeviceEntryUdfpsRefactor.token,
             // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index 94c94e2..aa418e6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.scene.shared.logger
 
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
@@ -94,6 +95,34 @@
         }
     }
 
+    fun logOverlayChangeRequested(
+        from: OverlayKey? = null,
+        to: OverlayKey? = null,
+        reason: String,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from?.toString()
+                str2 = to?.toString()
+                str3 = reason
+            },
+            messagePrinter = {
+                buildString {
+                    append("Overlay change requested: ")
+                    if (str1 != null) {
+                        append(str1)
+                        append(if (str2 == null) " (hidden)" else " → $str2")
+                    } else {
+                        append("$str2 (shown)")
+                    }
+                    append(", reason: $str3")
+                }
+            },
+        )
+    }
+
     fun logVisibilityChange(
         from: Boolean,
         to: Boolean,
@@ -115,7 +144,7 @@
         )
     }
 
-    fun logRemoteUserInteractionStarted(
+    fun logRemoteUserInputStarted(
         reason: String,
     ) {
         logBuffer.log(
@@ -126,7 +155,7 @@
         )
     }
 
-    fun logUserInteractionFinished() {
+    fun logUserInputFinished() {
         logBuffer.log(
             tag = TAG,
             level = LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 61a06db..8e2e8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -20,7 +20,6 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.lifecycle.Activatable
-import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -36,10 +35,6 @@
     /** Uniquely-identifying key for this scene. The key must be unique within its container. */
     val key: SceneKey
 
-    override suspend fun activate(): Nothing {
-        awaitCancellation()
-    }
-
     /**
      * The mapping between [UserAction] and destination [UserActionResult]s.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 0a30c31..2311e47 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 
 /** Models the configuration of the scene container. */
@@ -38,6 +39,13 @@
     val initialSceneKey: SceneKey,
 
     /**
+     * The keys to all overlays in the container, sorted by z-order such that the last one renders
+     * on top of all previous ones. Overlay keys within the same container must not repeat but it's
+     * okay to have the same overlay keys in different containers.
+     */
+    val overlayKeys: List<OverlayKey> = emptyList(),
+
+    /**
      * Navigation distance of each scene.
      *
      * The navigation distance is a measure of how many non-back user action "steps" away from the
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
index 034da25..4538d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import kotlinx.coroutines.flow.StateFlow
@@ -33,6 +34,14 @@
     val currentScene: StateFlow<SceneKey>
 
     /**
+     * The current set of overlays to be shown (may be empty).
+     *
+     * Note that during a transition between overlays, a different set of overlays may be rendered -
+     * but only the ones in this set are considered the current overlays.
+     */
+    val currentOverlays: StateFlow<Set<OverlayKey>>
+
+    /**
      * Asks for an asynchronous scene switch to [toScene], which will use the corresponding
      * installed transition or the one specified by [transitionKey], if provided.
      */
@@ -47,4 +56,40 @@
     fun snapToScene(
         toScene: SceneKey,
     )
+
+    /**
+     * Request to show [overlay] so that it animates in from [currentScene] and ends up being
+     * visible on screen.
+     *
+     * After this returns, this overlay will be included in [currentOverlays]. This does nothing if
+     * [overlay] is already shown.
+     */
+    fun showOverlay(
+        overlay: OverlayKey,
+        transitionKey: TransitionKey? = null,
+    )
+
+    /**
+     * Request to hide [overlay] so that it animates out to [currentScene] and ends up *not* being
+     * visible on screen.
+     *
+     * After this returns, this overlay will not be included in [currentOverlays]. This does nothing
+     * if [overlay] is already hidden.
+     */
+    fun hideOverlay(
+        overlay: OverlayKey,
+        transitionKey: TransitionKey? = null,
+    )
+
+    /**
+     * Replace [from] by [to] so that [from] ends up not being visible on screen and [to] ends up
+     * being visible.
+     *
+     * This throws if [from] is not currently shown or if [to] is already shown.
+     */
+    fun replaceOverlay(
+        from: OverlayKey,
+        to: OverlayKey,
+        transitionKey: TransitionKey? = null,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index 43c3635..eb4c0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import kotlinx.coroutines.CoroutineScope
@@ -49,6 +50,15 @@
                 initialValue = config.initialSceneKey,
             )
 
+    override val currentOverlays: StateFlow<Set<OverlayKey>> =
+        delegateMutable
+            .flatMapLatest { delegate -> delegate.currentOverlays }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = emptySet(),
+            )
+
     override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
         delegateMutable.value.changeScene(
             toScene = toScene,
@@ -62,6 +72,28 @@
         )
     }
 
+    override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        delegateMutable.value.showOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        delegateMutable.value.hideOverlay(
+            overlay = overlay,
+            transitionKey = transitionKey,
+        )
+    }
+
+    override fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey?) {
+        delegateMutable.value.replaceOverlay(
+            from = from,
+            to = to,
+            transitionKey = transitionKey,
+        )
+    }
+
     /**
      * Binds the current, dependency injection provided [SceneDataSource] to the given object.
      *
@@ -82,8 +114,21 @@
         override val currentScene: StateFlow<SceneKey> =
             MutableStateFlow(initialSceneKey).asStateFlow()
 
+        override val currentOverlays: StateFlow<Set<OverlayKey>> =
+            MutableStateFlow(emptySet<OverlayKey>()).asStateFlow()
+
         override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
 
         override fun snapToScene(toScene: SceneKey) = Unit
+
+        override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) = Unit
+
+        override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) = Unit
+
+        override fun replaceOverlay(
+            from: OverlayKey,
+            to: OverlayKey,
+            transitionKey: TransitionKey?
+        ) = Unit
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 8aa601f..c1bb6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
+import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.shade.TouchLogger
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -35,6 +36,7 @@
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
         scenes: Set<Scene>,
+        overlays: Set<Overlay>,
         layoutInsetController: LayoutInsetsController,
         sceneDataSourceDelegator: SceneDataSourceDelegator,
         alternateBouncerDependencies: AlternateBouncerDependencies,
@@ -50,6 +52,7 @@
             containerConfig = containerConfig,
             sharedNotificationContainer = sharedNotificationContainer,
             scenes = scenes,
+            overlays = overlays,
             onVisibilityChangedInternal = { isVisible ->
                 super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
             },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 0f05af6..ec6513a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.policy.ScreenDecorationsUtils
@@ -47,6 +48,7 @@
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.scene.ui.composable.SceneContainer
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -70,6 +72,7 @@
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
         scenes: Set<Scene>,
+        overlays: Set<Overlay>,
         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
         dataSourceDelegator: SceneDataSourceDelegator,
         alternateBouncerDependencies: AlternateBouncerDependencies,
@@ -86,8 +89,22 @@
             }
         }
 
+        val unsortedOverlayByKey: Map<OverlayKey, Overlay> =
+            overlays.associateBy { overlay -> overlay.key }
+        val sortedOverlayByKey: Map<OverlayKey, Overlay> = buildMap {
+            containerConfig.overlayKeys.forEach { overlayKey ->
+                val overlay =
+                    checkNotNull(unsortedOverlayByKey[overlayKey]) {
+                        "Overlay not found for key \"$overlayKey\"!"
+                    }
+
+                put(overlayKey, overlay)
+            }
+        }
+
         view.repeatWhenAttached {
             view.viewModel(
+                traceName = "SceneWindowRootViewBinder",
                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                 factory = { viewModelFactory.create(motionEventHandlerReceiver) },
             ) { viewModel ->
@@ -112,6 +129,7 @@
                                 viewModel = viewModel,
                                 windowInsets = windowInsets,
                                 sceneByKey = sortedSceneByKey,
+                                overlayByKey = sortedOverlayByKey,
                                 dataSourceDelegator = dataSourceDelegator,
                                 containerConfig = containerConfig,
                             )
@@ -156,6 +174,7 @@
         viewModel: SceneContainerViewModel,
         windowInsets: StateFlow<WindowInsets?>,
         sceneByKey: Map<SceneKey, Scene>,
+        overlayByKey: Map<OverlayKey, Overlay>,
         dataSourceDelegator: SceneDataSourceDelegator,
         containerConfig: SceneContainerConfig,
     ): View {
@@ -170,6 +189,7 @@
                             viewModel = viewModel,
                             sceneByKey =
                                 sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+                            overlayByKey = overlayByKey,
                             initialSceneKey = containerConfig.initialSceneKey,
                             dataSourceDelegator = dataSourceDelegator,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
index b707a5a..88d4c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 
 class GoneSceneActionsViewModel
@@ -82,7 +81,7 @@
                     }
                 }
             }
-            .collectLatest { setActions(it) }
+            .collect { setActions(it) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index b5de1b6..368e4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -32,7 +32,7 @@
  * need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
  * this base class takes care of it.
  */
-abstract class SceneActionsViewModel : SysUiViewModel() {
+abstract class SceneActionsViewModel : ExclusiveActivatable() {
 
     private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index f8a9f8c..a73c39d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -24,15 +24,16 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -45,20 +46,15 @@
     private val powerInteractor: PowerInteractor,
     private val logger: SceneLogger,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
-) : SysUiViewModel() {
-    /**
-     * Keys of all scenes in the container.
-     *
-     * The scenes will be sorted in z-order such that the last one is the one that should be
-     * rendered on top of all previous ones.
-     */
-    val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys()
+) : ExclusiveActivatable() {
 
     /** The scene that should be rendered. */
     val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene
 
+    private val hydrator = Hydrator("SceneContainerViewModel.hydrator")
+
     /** Whether the container is visible. */
-    val isVisible: Boolean by hydratedStateOf(sceneInteractor.isVisible)
+    val isVisible: Boolean by hydrator.hydratedStateOf("isVisible", sceneInteractor.isVisible)
 
     override suspend fun onActivated(): Nothing {
         try {
@@ -75,7 +71,8 @@
                     }
                 }
             )
-            awaitCancellation()
+
+            hydrator.activate()
         } finally {
             // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
             // their reference to it.
@@ -93,7 +90,9 @@
     }
 
     /**
-     * Notifies that a [MotionEvent] is first seen at the top of the scene container UI.
+     * Notifies that a [MotionEvent] is first seen at the top of the scene container UI. This
+     * includes gestures on [SharedNotificationContainer] as well as the Composable scene container
+     * hierarchy.
      *
      * Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
      */
@@ -104,11 +103,21 @@
             event.actionMasked == MotionEvent.ACTION_UP ||
                 event.actionMasked == MotionEvent.ACTION_CANCEL
         ) {
-            sceneInteractor.onUserInteractionFinished()
+            sceneInteractor.onUserInputFinished()
         }
     }
 
     /**
+     * Notifies that a scene container user interaction has begun.
+     *
+     * This is a user interaction that has reached the Composable hierarchy of the scene container,
+     * rather than being handled by [SharedNotificationContainer].
+     */
+    fun onSceneContainerUserInputStarted() {
+        sceneInteractor.onSceneContainerUserInputStarted()
+    }
+
+    /**
      * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through
      * the scene container UI.
      *
@@ -170,8 +179,20 @@
         actionResultMap: Map<UserAction, UserActionResult>,
     ): Map<UserAction, UserActionResult> {
         return actionResultMap.mapValues { (_, actionResult) ->
-            sceneInteractor.resolveSceneFamilyOrNull(actionResult.toScene)?.value?.let {
-                actionResult.copy(toScene = it)
+            when (actionResult) {
+                is UserActionResult.ChangeScene -> {
+                    sceneInteractor.resolveSceneFamilyOrNull(actionResult.toScene)?.value?.let {
+                        toScene ->
+                        UserActionResult(
+                            toScene = toScene,
+                            transitionKey = actionResult.transitionKey,
+                            requiresFullDistanceSwipe = actionResult.requiresFullDistanceSwipe,
+                        )
+                    }
+                }
+                is UserActionResult.ShowOverlay,
+                is UserActionResult.HideOverlay,
+                is UserActionResult.ReplaceByOverlay -> TODO("b/353679003: Support overlays")
             } ?: actionResult
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 0a1f649..ed590c3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -36,6 +36,13 @@
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.Assert
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import javax.inject.Provider
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -44,30 +51,23 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.sync.Mutex
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
-import javax.inject.Provider
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
 
 /**
  * SystemUI cache for keeping track of the current user and associated values.
  *
- * The values provided asynchronously are NOT copies, but shared among all requesters. Do not
- * modify them.
+ * The values provided asynchronously are NOT copies, but shared among all requesters. Do not modify
+ * them.
  *
  * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as
- * soon as possible (and reduce its dependency graph).
- * Other classes that want to listen to the broadcasts listened here SHOULD
- * subscribe to this class instead.
+ * soon as possible (and reduce its dependency graph). Other classes that want to listen to the
+ * broadcasts listened here SHOULD subscribe to this class instead.
  *
  * @see UserTracker
  *
  * Class constructed and initialized in [SettingsModule].
  */
-open class UserTrackerImpl internal constructor(
+open class UserTrackerImpl
+internal constructor(
     private val context: Context,
     private val featureFlagsProvider: Provider<FeatureFlagsClassic>,
     private val userManager: UserManager,
@@ -87,8 +87,8 @@
         private set
 
     private val mutex = Any()
-    private val isBackgroundUserSwitchEnabled: Boolean get() =
-        featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
+    private val isBackgroundUserSwitchEnabled: Boolean
+        get() = featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
 
     @Deprecated("Use UserInteractor.getSelectedUserId()")
     override var userId: Int by SynchronizedDelegate(context.userId)
@@ -118,8 +118,7 @@
     override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
         protected set
 
-    @GuardedBy("callbacks")
-    private val callbacks: MutableList<DataItem> = ArrayList()
+    @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList()
 
     private var userSwitchingJob: Job? = null
     private var afterUserSwitchingJob: Job? = null
@@ -128,23 +127,25 @@
         if (initialized) {
             return
         }
+        Log.i(TAG, "Starting user: $startingUser")
         initialized = true
         setUserIdInternal(startingUser)
 
-        val filter = IntentFilter().apply {
-            addAction(Intent.ACTION_LOCALE_CHANGED)
-            addAction(Intent.ACTION_USER_INFO_CHANGED)
-            addAction(Intent.ACTION_PROFILE_ADDED)
-            addAction(Intent.ACTION_PROFILE_REMOVED)
-            addAction(Intent.ACTION_PROFILE_AVAILABLE)
-            addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
-            // These get called when a managed profile goes in or out of quiet mode.
-            addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
-            addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
-            addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
-            addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
-            addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
-        }
+        val filter =
+            IntentFilter().apply {
+                addAction(Intent.ACTION_LOCALE_CHANGED)
+                addAction(Intent.ACTION_USER_INFO_CHANGED)
+                addAction(Intent.ACTION_PROFILE_ADDED)
+                addAction(Intent.ACTION_PROFILE_REMOVED)
+                addAction(Intent.ACTION_PROFILE_AVAILABLE)
+                addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
+                // These get called when a managed profile goes in or out of quiet mode.
+                addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+                addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
+                addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+                addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
+            }
         context.registerReceiverForAllUsers(this, filter, null, backgroundHandler)
 
         registerUserSwitchObserver()
@@ -191,36 +192,39 @@
     }
 
     private fun registerUserSwitchObserver() {
-        iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
-            override fun onBeforeUserSwitching(newUserId: Int) {
-                handleBeforeUserSwitching(newUserId)
-            }
-
-            override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
-                if (isBackgroundUserSwitchEnabled) {
-                    userSwitchingJob?.cancel()
-                    userSwitchingJob = appScope.launch(backgroundContext) {
-                        handleUserSwitchingCoroutines(newUserId) {
-                            reply?.sendResult(null)
-                        }
-                    }
-                } else {
-                    handleUserSwitching(newUserId)
-                    reply?.sendResult(null)
+        iActivityManager.registerUserSwitchObserver(
+            object : UserSwitchObserver() {
+                override fun onBeforeUserSwitching(newUserId: Int) {
+                    handleBeforeUserSwitching(newUserId)
                 }
-            }
 
-            override fun onUserSwitchComplete(newUserId: Int) {
-                if (isBackgroundUserSwitchEnabled) {
-                    afterUserSwitchingJob?.cancel()
-                    afterUserSwitchingJob = appScope.launch(backgroundContext) {
+                override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
+                    if (isBackgroundUserSwitchEnabled) {
+                        userSwitchingJob?.cancel()
+                        userSwitchingJob =
+                            appScope.launch(backgroundContext) {
+                                handleUserSwitchingCoroutines(newUserId) { reply?.sendResult(null) }
+                            }
+                    } else {
+                        handleUserSwitching(newUserId)
+                        reply?.sendResult(null)
+                    }
+                }
+
+                override fun onUserSwitchComplete(newUserId: Int) {
+                    if (isBackgroundUserSwitchEnabled) {
+                        afterUserSwitchingJob?.cancel()
+                        afterUserSwitchingJob =
+                            appScope.launch(backgroundContext) {
+                                handleUserSwitchComplete(newUserId)
+                            }
+                    } else {
                         handleUserSwitchComplete(newUserId)
                     }
-                } else {
-                    handleUserSwitchComplete(newUserId)
                 }
-            }
-        }, TAG)
+            },
+            TAG
+        )
     }
 
     @WorkerThread
@@ -228,9 +232,10 @@
         setUserIdInternal(newUserId)
 
         notifySubscribers { callback, resultCallback ->
-            callback.onBeforeUserSwitching(newUserId)
-            resultCallback.run()
-        }.await()
+                callback.onBeforeUserSwitching(newUserId)
+                resultCallback.run()
+            }
+            .await()
     }
 
     @WorkerThread
@@ -239,31 +244,34 @@
         Log.i(TAG, "Switching to user $newUserId")
 
         notifySubscribers { callback, resultCallback ->
-            callback.onUserChanging(newUserId, userContext, resultCallback)
-        }.await()
+                callback.onUserChanging(newUserId, userContext, resultCallback)
+            }
+            .await()
     }
 
     @WorkerThread
     protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) =
-            coroutineScope {
-                Assert.isNotMainThread()
-                Log.i(TAG, "Switching to user $newUserId")
+        coroutineScope {
+            Assert.isNotMainThread()
+            Log.i(TAG, "Switching to user $newUserId")
 
-                for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) {
-                    val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue
-                    launch(callbackDataItem.executor.asCoroutineDispatcher()) {
+            for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) {
+                val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue
+                launch(callbackDataItem.executor.asCoroutineDispatcher()) {
                         val mutex = Mutex(true)
-                        val thresholdLogJob = launch(backgroundContext) {
-                            delay(USER_CHANGE_THRESHOLD)
-                            Log.e(TAG, "Failed to finish $callback in time")
-                        }
+                        val thresholdLogJob =
+                            launch(backgroundContext) {
+                                delay(USER_CHANGE_THRESHOLD)
+                                Log.e(TAG, "Failed to finish $callback in time")
+                            }
                         callback.onUserChanging(userId, userContext) { mutex.unlock() }
                         mutex.lock()
                         thresholdLogJob.cancel()
-                    }.join()
-                }
-                onDone()
+                    }
+                    .join()
             }
+            onDone()
+        }
 
     @WorkerThread
     protected open fun handleUserSwitchComplete(newUserId: Int) {
@@ -284,36 +292,26 @@
         synchronized(mutex) {
             userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy
         }
-        notifySubscribers { callback, _ ->
-            callback.onProfilesChanged(profiles)
-        }
+        notifySubscribers { callback, _ -> callback.onProfilesChanged(profiles) }
     }
 
     override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
-        synchronized(callbacks) {
-            callbacks.add(DataItem(WeakReference(callback), executor))
-        }
+        synchronized(callbacks) { callbacks.add(DataItem(WeakReference(callback), executor)) }
     }
 
     override fun removeCallback(callback: UserTracker.Callback) {
-        synchronized(callbacks) {
-            callbacks.removeIf { it.sameOrEmpty(callback) }
-        }
+        synchronized(callbacks) { callbacks.removeIf { it.sameOrEmpty(callback) } }
     }
 
     private inline fun notifySubscribers(
-            crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit
+        crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit
     ): CountDownLatch {
-        val list = synchronized(callbacks) {
-            callbacks.toList()
-        }
+        val list = synchronized(callbacks) { callbacks.toList() }
         val latch = CountDownLatch(list.size)
         list.forEach {
             val callback = it.callback.get()
             if (callback != null) {
-                it.executor.execute {
-                    action(callback) { latch.countDown() }
-                }
+                it.executor.execute { action(callback) { latch.countDown() } }
             } else {
                 latch.countDown()
             }
@@ -328,20 +326,13 @@
             val ids = userProfiles.map { it.toFullString() }
             pw.println("userProfiles: $ids")
         }
-        val list = synchronized(callbacks) {
-            callbacks.toList()
-        }
+        val list = synchronized(callbacks) { callbacks.toList() }
         pw.println("Callbacks:")
-        list.forEach {
-            it.callback.get()?.let {
-                pw.println("  $it")
-            }
-        }
+        list.forEach { it.callback.get()?.let { pw.println("  $it") } }
     }
 
-    private class SynchronizedDelegate<T : Any>(
-        private var value: T
-    ) : ReadWriteProperty<UserTrackerImpl, T> {
+    private class SynchronizedDelegate<T : Any>(private var value: T) :
+        ReadWriteProperty<UserTrackerImpl, T> {
 
         @GuardedBy("mutex")
         override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 7f8c146..52bc25d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -20,7 +20,6 @@
 import android.util.Log
 import android.view.View
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.res.R
 import com.android.systemui.settings.brightness.BrightnessSliderController
 import com.android.systemui.settings.brightness.MirrorController
@@ -37,7 +36,7 @@
     private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
     @Main private val resources: Resources,
     val sliderControllerFactory: BrightnessSliderController.Factory,
-) : SysUiViewModel(), MirrorController {
+) : MirrorController {
 
     private val tempPosition = IntArray(2)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 181c3df..3bb494b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.content.Context
-import android.graphics.Insets
 import android.graphics.Rect
 import android.os.PowerManager
 import android.os.SystemClock
@@ -26,13 +25,14 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
-import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.lifecycleScope
@@ -40,7 +40,6 @@
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Flags
-import com.android.systemui.Flags.glanceableHubBackGesture
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.communal.dagger.Communal
@@ -55,6 +54,9 @@
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalTouchLog
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -91,8 +93,10 @@
     @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
     private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
     private val keyguardMediaController: KeyguardMediaController,
-    private val lockscreenSmartspaceController: LockscreenSmartspaceController
+    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    @CommunalTouchLog logBuffer: LogBuffer,
 ) : LifecycleOwner {
+    private val logger = Logger(logBuffer, "GlanceableHubContainerController")
 
     private class CommunalWrapper(context: Context) : FrameLayout(context) {
         private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
@@ -143,6 +147,17 @@
     private var isTrackingHubTouch = false
 
     /**
+     * True if a touch gesture on the lock screen has been consumed by the shade/bouncer and thus
+     * should be ignored by the hub.
+     *
+     * This is necessary on the lock screen as gestures on an empty spot go through special touch
+     * handling logic in [NotificationShadeWindowViewController] that decides if they should go to
+     * the shade or bouncer. Once the shade or bouncer are moving, we don't get the typical cancel
+     * event so to play nice, we ignore touches once we see the shade or bouncer are opening.
+     */
+    private var touchTakenByKeyguardGesture = false
+
+    /**
      * True if the hub UI is fully open, meaning it should receive touch input.
      *
      * Tracks [CommunalInteractor.isCommunalShowing].
@@ -188,6 +203,13 @@
      */
     private var shadeConsumingTouches = false
 
+    /**
+     * True if the shade is showing at all.
+     *
+     * Inverse of [ShadeInteractor.isShadeFullyCollapsed]
+     */
+    private var shadeShowing = false
+
     /** True if the keyguard transition state is finished on [KeyguardState.LOCKSCREEN]. */
     private var onLockscreen = false
 
@@ -206,6 +228,21 @@
      */
     private var isDreaming = false
 
+    /** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */
+    private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event ->
+        logger.d({
+            "Touch handler lifecycle changed to $str1. hubShowing: $bool1, " +
+                "shadeShowingAndConsumingTouches: $bool2, " +
+                "anyBouncerShowing: $bool3, inEditModeTransition: $bool4"
+        }) {
+            str1 = event.toString()
+            bool1 = hubShowing
+            bool2 = shadeShowingAndConsumingTouches
+            bool3 = anyBouncerShowing
+            bool4 = inEditModeTransition
+        }
+    }
+
     /** Returns a flow that tracks whether communal hub is available. */
     fun communalAvailable(): Flow<Boolean> =
         anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
@@ -268,6 +305,7 @@
                     init()
                 }
         }
+        lifecycleRegistry.addObserver(touchLifecycleLogger)
         lifecycleRegistry.currentState = Lifecycle.State.CREATED
 
         communalContainerView = containerView
@@ -288,21 +326,13 @@
             // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
             // occluded.
             lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-                // Avoid adding exclusion to end/start edges to allow back gestures.
-                val insets =
-                    if (glanceableHubBackGesture()) {
-                        containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
-                    } else {
-                        Insets.NONE
-                    }
-
                 val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR
 
                 val backGestureInset =
                     Rect(
-                        if (ltr) 0 else insets.left,
                         0,
-                        if (ltr) insets.right else containerView.right,
+                        0,
+                        if (ltr) 0 else containerView.right,
                         containerView.bottom,
                     )
 
@@ -318,9 +348,9 @@
                             // Only allow swipe up to bouncer and swipe down to shade in the very
                             // top/bottom to avoid conflicting with widgets in the hub grid.
                             Rect(
-                                insets.left,
+                                0,
                                 topEdgeSwipeRegionWidth,
-                                containerView.right - insets.right,
+                                containerView.right,
                                 containerView.bottom - bottomEdgeSwipeRegionWidth
                             ),
                             // Disable back gestures on the left side of the screen, to avoid
@@ -328,6 +358,9 @@
                             backGestureInset
                         )
                     }
+                logger.d({ "Insets updated: $str1" }) {
+                    str1 = containerView.systemGestureExclusionRects.toString()
+                }
             }
         }
 
@@ -343,6 +376,9 @@
             ),
             {
                 anyBouncerShowing = it
+                if (hubShowing) {
+                    logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it }
+                }
                 updateTouchHandlingState()
             }
         )
@@ -385,6 +421,7 @@
             ),
             { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
                 shadeConsumingTouches = isUserInteracting
+                shadeShowing = !isShadeFullyCollapsed
                 val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
 
                 // If we ever are fully expanded and not interacting, capture this state as we
@@ -396,7 +433,13 @@
                 // If the shade reaches full expansion without interaction, then we should allow it
                 // to consume touches rather than handling it here until it disappears.
                 shadeShowingAndConsumingTouches =
-                    userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
+                    (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive).also {
+                        if (it != shadeShowingAndConsumingTouches && hubShowing) {
+                            logger.d({ "New value for shadeShowingAndConsumingTouches: $bool1" }) {
+                                bool1 = it
+                            }
+                        }
+                    }
                 updateTouchHandlingState()
             }
         )
@@ -404,6 +447,7 @@
 
         communalContainerWrapper = CommunalWrapper(containerView.context)
         communalContainerWrapper?.addView(communalContainerView)
+        logger.d("Hub container initialized")
         return communalContainerWrapper!!
     }
 
@@ -446,6 +490,10 @@
             (it.parent as ViewGroup).removeView(it)
             communalContainerWrapper = null
         }
+
+        lifecycleRegistry.removeObserver(touchLifecycleLogger)
+
+        logger.d("Hub container disposed")
     }
 
     /**
@@ -463,15 +511,20 @@
         // In the case that we are handling full swipes on the lockscreen, are on the lockscreen,
         // and the touch is within the horizontal notification band on the screen, do not process
         // the touch.
-        if (
-            !hubShowing &&
-                (!notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) ||
-                    keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) ||
-                    lockscreenSmartspaceController.isWithinSmartspaceBounds(
-                        ev.x.toInt(),
-                        ev.y.toInt()
-                    ))
-        ) {
+        val touchOnNotifications =
+            !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+        val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
+        val touchOnSmartspace =
+            lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
+        if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+            logger.d({
+                "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
+                    "touchOnSmartspace: $bool3"
+            }) {
+                bool1 = touchOnNotifications
+                bool2 = touchOnUmo
+                bool3 = touchOnSmartspace
+            }
             return false
         }
 
@@ -484,15 +537,59 @@
         val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE
         val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
 
-        val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
+        val hubOccluded = anyBouncerShowing || shadeConsumingTouches || shadeShowing
 
         if ((isDown || isMove) && !hubOccluded) {
+            if (isDown) {
+                logger.d({
+                    "Touch started. x: $int1, y: $int2, hubShowing: $bool1, isDreaming: $bool2, " +
+                        "onLockscreen: $bool3"
+                }) {
+                    int1 = ev.x.toInt()
+                    int2 = ev.y.toInt()
+                    bool1 = hubShowing
+                    bool2 = isDreaming
+                    bool3 = onLockscreen
+                }
+            }
             isTrackingHubTouch = true
         }
 
         if (isTrackingHubTouch) {
+            // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
+            // touch handling for gestures on blank areas, which can go up to show the bouncer or
+            // down to show the notification shade. We see the touches first and they are not
+            // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
+            // if the shade or bouncer are handling them. This issue only applies to touches on the
+            // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
+            // taking touches.
+            touchTakenByKeyguardGesture =
+                (onLockscreen && (shadeConsumingTouches || anyBouncerShowing)).also {
+                    if (it != touchTakenByKeyguardGesture && it) {
+                        logger.d(
+                            "Lock screen touch consumed by shade or bouncer, ignoring " +
+                                "subsequent touches"
+                        )
+                    }
+                }
             if (isUp || isCancel) {
+                logger.d({
+                    val endReason = if (bool1) "up" else "cancel"
+                    "Touch ended with $endReason. x: $int1, y: $int2, " +
+                        "shadeConsumingTouches: $bool2, anyBouncerShowing: $bool3"
+                }) {
+                    int1 = ev.x.toInt()
+                    int2 = ev.y.toInt()
+                    bool1 = isUp
+                    bool2 = shadeConsumingTouches
+                    bool3 = anyBouncerShowing
+                }
                 isTrackingHubTouch = false
+
+                // Clear out touch taken state to ensure the up/cancel event still gets dispatched
+                // to the hub. This is necessary as the hub always receives at least the initial
+                // down even if the shade or bouncer end up handling the touch.
+                touchTakenByKeyguardGesture = false
             }
             return dispatchTouchEvent(ev)
         }
@@ -513,21 +610,8 @@
             return true
         }
         try {
-            // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
-            // touch handling for gestures on blank areas, which can go up to show the bouncer or
-            // down to show the notification shade. We see the touches first and they are not
-            // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
-            // if the shade or bouncer are handling them. This issue only applies to touches on the
-            // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
-            // taking touches.
-            val touchTaken = onLockscreen && (shadeConsumingTouches || anyBouncerShowing)
-
-            // Only dispatch touches to communal if not already handled or the touch is ending,
-            // meaning the event is an up or cancel. This is necessary as the hub always receives at
-            // least the initial down even if the shade or bouncer end up handling the touch.
-            val dispatchToCommunal = !touchTaken || !isTrackingHubTouch
             var handled = false
-            if (dispatchToCommunal) {
+            if (!touchTakenByKeyguardGesture) {
                 communalContainerWrapper?.dispatchTouchEvent(ev) {
                     if (it) {
                         handled = true
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c023b83..31813b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,12 +191,10 @@
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -439,7 +437,6 @@
     private boolean mExpandingFromHeadsUp;
     private boolean mCollapsedOnDown;
     private boolean mClosingWithAlphaFadeOut;
-    private boolean mHeadsUpVisible;
     private boolean mHeadsUpAnimatingAway;
     private final FalsingManager mFalsingManager;
     private final FalsingCollector mFalsingCollector;
@@ -610,7 +607,6 @@
     private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
     private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
-    private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
@@ -776,7 +772,6 @@
             ActivityStarter activityStarter,
             SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
             ActiveNotificationsInteractor activeNotificationsInteractor,
-            HeadsUpNotificationInteractor headsUpNotificationInteractor,
             ShadeAnimationInteractor shadeAnimationInteractor,
             KeyguardViewConfigurator keyguardViewConfigurator,
             DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -811,7 +806,6 @@
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
         mActiveNotificationsInteractor = activeNotificationsInteractor;
-        mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1222,11 +1216,6 @@
                     }
                 },
                 mMainDispatcher);
-
-        if (NotificationsHeadsUpRefactor.isEnabled()) {
-            collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
-                    setHeadsUpVisible(), mMainDispatcher);
-        }
     }
 
     @VisibleForTesting
@@ -3077,21 +3066,7 @@
         mPanelAlphaEndAction = r;
     }
 
-    private Consumer<Boolean> setHeadsUpVisible() {
-        return (Boolean isHeadsUpVisible) -> {
-            mHeadsUpVisible = isHeadsUpVisible;
-
-            if (isHeadsUpVisible) {
-                updateNotificationTranslucency();
-            }
-            updateExpansionAndVisibility();
-            updateGestureExclusionRect();
-            mKeyguardStatusBarViewController.updateForHeadsUp();
-        };
-    }
-
     private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
         mHeadsUpAnimatingAway = headsUpAnimatingAway;
         mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
         updateVisibility();
@@ -3107,16 +3082,13 @@
     }
 
     private boolean shouldPanelBeVisible() {
-        boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
-                : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+        boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
         return headsUpVisible || isExpanded() || mBouncerShowing;
     }
 
     private void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
-            mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
-        }
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(
                 headsUpManager,
                 mStatusBarService,
@@ -3204,8 +3176,7 @@
     }
 
     private boolean isPanelVisibleBecauseOfHeadsUp() {
-        boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
-                : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+        boolean headsUpVisible = mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
         return headsUpVisible && mBarState == StatusBarState.SHADE;
     }
 
@@ -3521,7 +3492,6 @@
         ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
         ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
         ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
-        ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
         ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
         ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
         ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4446,8 +4416,6 @@
     private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-            NotificationsHeadsUpRefactor.assertInLegacyMode();
-
             if (inPinnedMode) {
                 mHeadsUpExistenceChangedRunnable.run();
                 updateNotificationTranslucency();
@@ -4464,8 +4432,6 @@
 
         @Override
         public void onHeadsUpPinned(NotificationEntry entry) {
-            NotificationsHeadsUpRefactor.assertInLegacyMode();
-
             if (!isKeyguardShowing()) {
                 mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
             }
@@ -4473,8 +4439,6 @@
 
         @Override
         public void onHeadsUpUnPinned(NotificationEntry entry) {
-            NotificationsHeadsUpRefactor.assertInLegacyMode();
-
             // When we're unpinning the notification via active edge they remain heads-upped,
             // we need to make sure that an animation happens in this case, otherwise the
             // notification
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 606fef0..018144b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.shade
 
 import android.annotation.SuppressLint
@@ -38,6 +40,7 @@
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
+import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.scene.ui.view.SceneWindowRootView
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -59,6 +62,7 @@
 import dagger.Provides
 import javax.inject.Named
 import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Module for providing views related to the shade. */
 @Module
@@ -82,6 +86,7 @@
             viewModelFactory: SceneContainerViewModel.Factory,
             containerConfigProvider: Provider<SceneContainerConfig>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
+            overlaysProvider: Provider<Set<@JvmSuppressWildcards Overlay>>,
             layoutInsetController: NotificationInsetsController,
             sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
             alternateBouncerDependencies: Provider<AlternateBouncerDependencies>,
@@ -96,6 +101,7 @@
                     sharedNotificationContainer =
                         sceneWindowRootView.requireViewById(R.id.shared_notification_container),
                     scenes = scenesProvider.get(),
+                    overlays = overlaysProvider.get(),
                     layoutInsetController = layoutInsetController,
                     sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
                     alternateBouncerDependencies = alternateBouncerDependencies.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 7d67121..e276f88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -64,7 +64,7 @@
                             0f
                         }
                     )
-                is ObservableTransitionState.Transition.ChangeCurrentScene ->
+                is ObservableTransitionState.Transition.ChangeScene ->
                     when {
                         state.fromScene == Scenes.Gone ->
                             if (state.toScene.isExpandable()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
index 2f98488..9c4bf1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
-import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
@@ -32,11 +34,40 @@
 @Inject
 constructor(
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    keyguardInteractor: KeyguardInteractor,
 ) {
+    /**
+     * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning
+     * between those states. Every permutation is listed so we can use optimal flows and support
+     * Scenes.
+     */
     val isKeyguardOccluded: Flow<Boolean> =
-        anyOf(
-            keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
-            keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
-        )
+        listOf(
+                // Finished in state...
+                keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
+                keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
+                keyguardTransitionInteractor.transitionValue(Scenes.Communal, GLANCEABLE_HUB).map {
+                    it == 1f
+                },
+
+                // ... or transitions between those states
+                keyguardTransitionInteractor.isInTransition(Edge.create(OCCLUDED, DREAMING)),
+                keyguardTransitionInteractor.isInTransition(Edge.create(DREAMING, OCCLUDED)),
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = OCCLUDED, to = Scenes.Communal),
+                    edgeWithoutSceneContainer = Edge.create(from = OCCLUDED, to = GLANCEABLE_HUB),
+                ),
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = Scenes.Communal, to = OCCLUDED),
+                    edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED),
+                ),
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = DREAMING, to = Scenes.Communal),
+                    edgeWithoutSceneContainer = Edge.create(from = DREAMING, to = GLANCEABLE_HUB),
+                ),
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+                    edgeWithoutSceneContainer = Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+                ),
+            )
+            .any()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 00c0235..abf1f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
@@ -28,7 +28,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 
 /**
  * Models UI state and handles user input for the overlay shade UI, which shows a shade as an
@@ -36,8 +35,10 @@
  */
 class OverlayShadeViewModel
 @AssistedInject
-constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
-    SysUiViewModel() {
+constructor(
+    private val sceneInteractor: SceneInteractor,
+    shadeInteractor: ShadeInteractor,
+) : ExclusiveActivatable() {
     private val _backgroundScene = MutableStateFlow(Scenes.Lockscreen)
     /** The scene to show in the background when the overlay shade is open. */
     val backgroundScene: StateFlow<SceneKey> = _backgroundScene.asStateFlow()
@@ -46,7 +47,7 @@
     val panelAlignment = shadeInteractor.shadeAlignment
 
     override suspend fun onActivated(): Nothing {
-        sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
+        sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collect { sceneKey ->
             _backgroundScene.value = sceneKey
         }
         awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index f0e9d41..a154e91 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -24,7 +24,7 @@
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
@@ -46,7 +46,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -65,7 +64,7 @@
     private val privacyChipInteractor: PrivacyChipInteractor,
     private val clockInteractor: ShadeHeaderClockInteractor,
     private val broadcastDispatcher: BroadcastDispatcher,
-) : SysUiViewModel() {
+) : ExclusiveActivatable() {
     /** True if there is exactly one mobile connection. */
     val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
 
@@ -132,12 +131,10 @@
             launch {
                 mobileIconsInteractor.filteredSubscriptions
                     .map { list -> list.map { it.subscriptionId } }
-                    .collectLatest { _mobileSubIds.value = it }
+                    .collect { _mobileSubIds.value = it }
             }
 
-            launch {
-                shadeInteractor.isQsEnabled.map { !it }.collectLatest { _isDisabled.value = it }
-            }
+            launch { shadeInteractor.isQsEnabled.map { !it }.collect { _isDisabled.value = it } }
 
             awaitCancellation()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
index bdc0fdb..ab71913 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 
 /**
@@ -67,7 +66,7 @@
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index fe3bcb5..7c70759 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -20,7 +20,7 @@
 
 import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -35,12 +35,10 @@
 import dagger.assisted.AssistedInject
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
 
 /**
  * Models UI state used to render the content of the shade scene.
@@ -61,7 +59,7 @@
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val sceneInteractor: SceneInteractor,
-) : SysUiViewModel() {
+) : ExclusiveActivatable() {
 
     val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
@@ -75,11 +73,9 @@
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
     override suspend fun onActivated(): Nothing {
-        deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+        deviceEntryInteractor.isDeviceEntered.collect { isDeviceEntered ->
             _isEmptySpaceClickable.value = !isDeviceEntered
         }
-
-        awaitCancellation()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3068460..6eadd26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -471,17 +471,7 @@
      */
     private Drawable getIcon(Context sysuiContext,
             Context context, StatusBarIcon statusBarIcon) {
-        int userId = statusBarIcon.user.getIdentifier();
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-        }
-
-        // Try to load the monochrome app icon if applicable
-        Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
-        // Otherwise, just use the icon normally
-        if (icon == null) {
-            icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
-        }
+        Drawable icon = loadDrawable(context, statusBarIcon);
 
         TypedValue typedValue = new TypedValue();
         sysuiContext.getResources().getValue(R.dimen.status_bar_icon_scale_factor,
@@ -509,6 +499,26 @@
     }
 
     @Nullable
+    private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
+        if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
+            return statusBarIcon.preloadedIcon.mutate();
+        } else {
+            int userId = statusBarIcon.user.getIdentifier();
+            if (userId == UserHandle.USER_ALL) {
+                userId = UserHandle.USER_SYSTEM;
+            }
+
+            // Try to load the monochrome app icon if applicable
+            Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
+            // Otherwise, just use the icon normally
+            if (icon == null) {
+                icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
+            }
+            return icon;
+        }
+    }
+
+    @Nullable
     private Drawable maybeGetMonochromeAppIcon(Context context,
             StatusBarIcon statusBarIcon) {
         if (android.app.Flags.notificationsUseMonochromeAppIcon()
@@ -1020,4 +1030,9 @@
     public boolean showsConversation() {
         return mShowsConversation;
     }
+
+    private static boolean usesModeIcons() {
+        return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+                && android.app.Flags.modesUiIcons();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 173ff37..be733d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -16,14 +16,24 @@
 
 package com.android.systemui.statusbar.chips
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 abstract class StatusBarChipsModule {
+    @Binds
+    @IntoMap
+    @ClassKey(DemoRonChipViewModel::class)
+    abstract fun binds(impl: DemoRonChipViewModel): CoreStartable
+
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 18ea0b4..e825258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -69,7 +69,7 @@
                                     state.notificationIconView
                                 )
                             } else {
-                                OngoingActivityChipModel.ChipIcon.Basic(phoneIcon)
+                                OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
                             }
 
                         // This block mimics OngoingCallController#updateChip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index cf4e707..d4ad6ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -190,7 +190,7 @@
     ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown.Timer(
             icon =
-                OngoingActivityChipModel.ChipIcon.Basic(
+                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
                     Icon.Resource(
                         CAST_TO_OTHER_DEVICE_ICON,
                         // This string is "Casting screen"
@@ -215,7 +215,7 @@
     private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown.IconOnly(
             icon =
-                OngoingActivityChipModel.ChipIcon.Basic(
+                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
                     Icon.Resource(
                         CAST_TO_OTHER_DEVICE_ICON,
                         // This string is just "Casting"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
new file mode 100644
index 0000000..84ccaec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.graphics.drawable.Drawable
+import com.android.systemui.CoreStartable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on
+ * adb commands sent by the user.
+ *
+ * Example adb commands:
+ *
+ * To show a chip with the SysUI icon and custom text:
+ * ```
+ * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min
+ * ```
+ *
+ * To hide the chip:
+ * ```
+ * adb shell cmd statusbar demo-ron --hide
+ * ```
+ *
+ * See [DemoRonCommand] for more information on the adb command spec.
+ */
+@SysUISingleton
+class DemoRonChipViewModel
+@Inject
+constructor(
+    private val commandRegistry: CommandRegistry,
+    private val packageManager: PackageManager,
+    private val systemClock: SystemClock,
+) : OngoingActivityChipViewModel, CoreStartable {
+    override fun start() {
+        commandRegistry.registerCommand("demo-ron") { DemoRonCommand() }
+    }
+
+    private val _chip =
+        MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+    override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow()
+
+    private inner class DemoRonCommand : ParseableCommand("demo-ron") {
+        private val packageName: String? by
+            param(
+                longName = "packageName",
+                shortName = "p",
+                description = "The package name for the demo RON app",
+                valueParser = Type.String,
+            )
+
+        private val text: String? by
+            param(
+                longName = "text",
+                shortName = "t",
+                description = "Text to display in the chip",
+                valueParser = Type.String,
+            )
+
+        private val hide by
+            flag(
+                longName = "hide",
+                description = "Hides any existing demo RON chip",
+            )
+
+        override fun execute(pw: PrintWriter) {
+            if (!StatusBarRonChips.isEnabled) {
+                pw.println(
+                    "Error: com.android.systemui.status_bar_ron_chips must be enabled " +
+                        "before using this demo feature"
+                )
+                return
+            }
+
+            if (hide) {
+                _chip.value = OngoingActivityChipModel.Hidden()
+                return
+            }
+
+            val currentPackageName = packageName
+            if (currentPackageName == null) {
+                pw.println("--packageName (or -p) must be included")
+                return
+            }
+
+            val appIcon = getAppIcon(currentPackageName)
+            if (appIcon == null) {
+                pw.println("Package $currentPackageName could not be found")
+                return
+            }
+
+            val currentText = text
+            if (currentText != null) {
+                _chip.value =
+                    OngoingActivityChipModel.Shown.Text(
+                        icon = appIcon,
+                        // TODO(b/361346412): Include a demo with a custom color theme.
+                        colors = ColorsModel.Themed,
+                        text = currentText,
+                    )
+            } else {
+                _chip.value =
+                    OngoingActivityChipModel.Shown.Timer(
+                        icon = appIcon,
+                        // TODO(b/361346412): Include a demo with a custom color theme.
+                        colors = ColorsModel.Themed,
+                        startTimeMs = systemClock.elapsedRealtime(),
+                        onClickListener = null,
+                    )
+            }
+        }
+
+        private fun getAppIcon(packageName: String): OngoingActivityChipModel.ChipIcon? {
+            lateinit var iconDrawable: Drawable
+            try {
+                // Note: For the real implementation, we should check if applicationInfo exists
+                // before fetching the icon, so that we either don't show the chip or show a good
+                // backup icon in case the app info can't be found for some reason.
+                iconDrawable = packageManager.getApplicationIcon(packageName)
+            } catch (e: NameNotFoundException) {
+                return null
+            }
+            return OngoingActivityChipModel.ChipIcon.FullColorAppIcon(
+                Icon.Loaded(drawable = iconDrawable, contentDescription = null),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
similarity index 72%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
index 62641fe..4ef1909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shared
+package com.android.systemui.statusbar.chips.ron.shared
 
 import com.android.systemui.Flags
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
-/** Helper for reading or using the notifications heads up refactor flag state. */
+/** Helper for reading or using the status bar RON chips flag state. */
 @Suppress("NOTHING_TO_INLINE")
-object NotificationsHeadsUpRefactor {
+object StatusBarRonChips {
     /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationsHeadsUpRefactor()
+        get() = Flags.statusBarRonChips()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -46,6 +46,14 @@
 
     /**
      * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
      * the flag is enabled to ensure that the refactor author catches issues in testing.
      */
     @JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 9e6cacb..eb73521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -80,7 +80,7 @@
                     is ScreenRecordChipModel.Recording -> {
                         OngoingActivityChipModel.Shown.Timer(
                             icon =
-                                OngoingActivityChipModel.ChipIcon.Basic(
+                                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
                                     Icon.Resource(
                                         ICON,
                                         ContentDescription.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 7897f93..d99a916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -110,7 +110,7 @@
     ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown.Timer(
             icon =
-                OngoingActivityChipModel.ChipIcon.Basic(
+                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
                     Icon.Resource(
                         SHARE_TO_APP_ICON,
                         ContentDescription.Resource(R.string.share_to_app_chip_accessibility_label),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 26a2f91..62622a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -89,6 +89,16 @@
         ) : Shown(icon = null, colors, onClickListener = null) {
             override val logName = "Shown.Countdown"
         }
+
+        /** This chip shows the specified [text] in the chip. */
+        data class Text(
+            override val icon: ChipIcon,
+            override val colors: ColorsModel,
+            // TODO(b/361346412): Enforce a max length requirement?
+            val text: String,
+        ) : Shown(icon, colors, onClickListener = null) {
+            override val logName = "Shown.Text"
+        }
     }
 
     /** Represents an icon to show on the chip. */
@@ -106,7 +116,13 @@
             }
         }
 
-        /** The icon is a basic resource or drawable icon that System UI created internally. */
-        data class Basic(val impl: Icon) : ChipIcon
+        /**
+         * This icon is a single color and it came from basic resource or drawable icon that System
+         * UI created internally.
+         */
+        data class SingleColorIcon(val impl: Icon) : ChipIcon
+
+        /** This icon is an app icon in full color (so it should not get tinted in any way). */
+        data class FullColorAppIcon(val impl: Icon) : ChipIcon
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index b0d897d..04c4516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -51,6 +53,7 @@
     shareToAppChipViewModel: ShareToAppChipViewModel,
     castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel,
     callChipViewModel: CallChipViewModel,
+    demoRonChipViewModel: DemoRonChipViewModel,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
     private enum class ChipType {
@@ -58,6 +61,8 @@
         ShareToApp,
         CastToOtherDevice,
         Call,
+        /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */
+        DemoRon,
     }
 
     /** Model that helps us internally track the various chip states from each of the types. */
@@ -78,6 +83,7 @@
             val shareToApp: OngoingActivityChipModel.Hidden,
             val castToOtherDevice: OngoingActivityChipModel.Hidden,
             val call: OngoingActivityChipModel.Hidden,
+            val demoRon: OngoingActivityChipModel.Hidden,
         ) : InternalChipModel
     }
 
@@ -87,7 +93,8 @@
             shareToAppChipViewModel.chip,
             castToOtherDeviceChipViewModel.chip,
             callChipViewModel.chip,
-        ) { screenRecord, shareToApp, castToOtherDevice, call ->
+            demoRonChipViewModel.chip,
+        ) { screenRecord, shareToApp, castToOtherDevice, call, demoRon ->
             logger.log(
                 TAG,
                 LogLevel.INFO,
@@ -98,7 +105,15 @@
                 },
                 { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
             )
-            logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
+            logger.log(
+                TAG,
+                LogLevel.INFO,
+                {
+                    str1 = call.logName
+                    str2 = demoRon.logName
+                },
+                { "... > Call=$str1 > DemoRon=$str2" }
+            )
             // This `when` statement shows the priority order of the chips.
             when {
                 // Screen recording also activates the media projection APIs, so whenever the
@@ -113,17 +128,23 @@
                     InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice)
                 call is OngoingActivityChipModel.Shown ->
                     InternalChipModel.Shown(ChipType.Call, call)
+                demoRon is OngoingActivityChipModel.Shown -> {
+                    StatusBarRonChips.assertInNewMode()
+                    InternalChipModel.Shown(ChipType.DemoRon, demoRon)
+                }
                 else -> {
                     // We should only get here if all chip types are hidden
                     check(screenRecord is OngoingActivityChipModel.Hidden)
                     check(shareToApp is OngoingActivityChipModel.Hidden)
                     check(castToOtherDevice is OngoingActivityChipModel.Hidden)
                     check(call is OngoingActivityChipModel.Hidden)
+                    check(demoRon is OngoingActivityChipModel.Hidden)
                     InternalChipModel.Hidden(
                         screenRecord = screenRecord,
                         shareToApp = shareToApp,
                         castToOtherDevice = castToOtherDevice,
                         call = call,
+                        demoRon = demoRon,
                     )
                 }
             }
@@ -154,6 +175,7 @@
                         ChipType.ShareToApp -> new.shareToApp
                         ChipType.CastToOtherDevice -> new.castToOtherDevice
                         ChipType.Call -> new.call
+                        ChipType.DemoRon -> new.demoRon
                     }
                 } else if (new is InternalChipModel.Shown) {
                     // If we have a chip to show, always show it.
@@ -179,6 +201,7 @@
                 shareToApp = OngoingActivityChipModel.Hidden(),
                 castToOtherDevice = OngoingActivityChipModel.Hidden(),
                 call = OngoingActivityChipModel.Hidden(),
+                demoRon = OngoingActivityChipModel.Hidden(),
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index ecb6d7f..406a664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
@@ -51,6 +52,11 @@
     @ClassKey(LightBarController::class)
     abstract fun bindLightBarController(impl: LightBarController): CoreStartable
 
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBarSignalPolicy::class)
+    abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 74ec7ed..aa203d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -21,11 +21,11 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -58,7 +58,7 @@
 
     /** Set of currently pinned top-level heads up rows to be displayed. */
     val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(emptySet())
         } else {
             headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
@@ -80,7 +80,7 @@
 
     /** Are there any pinned heads up rows to display? */
     val hasPinnedRows: Flow<Boolean> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
             headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
@@ -95,7 +95,7 @@
     }
 
     val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
             combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
@@ -123,7 +123,7 @@
         }
 
     val showHeadsUpStatusBar: Flow<Boolean> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
             combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index bf5b3a3..da29b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -22,6 +22,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
 import com.android.systemui.statusbar.notification.row.shared.IconModel
 import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
 import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
@@ -68,12 +69,13 @@
         builder: Notification.Builder,
         systemUIContext: Context,
         packageContext: Context
-    ): RichOngoingContentModel? =
+    ): RichOngoingContentModel? {
+        val sbn = entry.sbn
+        val notification = sbn.notification
+        val icon = IconModel(notification.smallIcon)
+
         try {
-            val sbn = entry.sbn
-            val notification = sbn.notification
-            val icon = IconModel(notification.smallIcon)
-            if (sbn.packageName == "com.google.android.deskclock") {
+            return if (sbn.packageName == "com.google.android.deskclock") {
                 when (notification.channelId) {
                     "Timers v2" -> {
                         parseTimerNotification(notification, icon)
@@ -87,11 +89,14 @@
                         null
                     }
                 }
+            } else if (builder.style is Notification.EnRouteStyle) {
+                parseEnRouteNotification(notification, icon)
             } else null
         } catch (e: Exception) {
             Log.e("RONs", "Error parsing RON", e)
-            null
+            return null
         }
+    }
 
     /**
      * FOR PROTOTYPING ONLY: create a RON TimerContentModel using the time information available
@@ -199,4 +204,15 @@
             .plusMinutes(minute.toLong())
             .plusSeconds(second.toLong())
     }
+
+    private fun parseEnRouteNotification(
+        notification: Notification,
+        icon: IconModel,
+    ): EnRouteContentModel {
+        return EnRouteContentModel(
+            smallIcon = icon,
+            title = notification.extras.getCharSequence(Notification.EXTRA_TITLE),
+            text = notification.extras.getCharSequence(Notification.EXTRA_TEXT),
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index 828fc21..2c462b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -27,12 +27,15 @@
 import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
 import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
 import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
 import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
 import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
-import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel
 import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
 import com.android.systemui.statusbar.notification.row.ui.view.TimerView
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder
 import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel
 import javax.inject.Inject
@@ -119,7 +122,15 @@
                     parentView,
                     viewType
                 )
-            is StopwatchContentModel -> TODO("Not yet implemented")
+            is EnRouteContentModel ->
+                inflateEnRouteView(
+                    existingView,
+                    component::createEnRouteViewModel,
+                    systemUiContext,
+                    parentView,
+                    viewType
+                )
+            else -> TODO("Not yet implemented")
         }
     }
 
@@ -131,7 +142,8 @@
         if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
         return when (contentModel) {
             is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
-            is StopwatchContentModel -> TODO("Not yet implemented")
+            is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
+            else -> TODO("Not yet implemented")
         }
     }
 
@@ -167,4 +179,38 @@
         existingView: View?,
         viewType: RichOngoingNotificationViewType
     ): Boolean = true
+
+    private fun inflateEnRouteView(
+        existingView: View?,
+        createViewModel: () -> EnRouteViewModel,
+        systemUiContext: Context,
+        parentView: ViewGroup,
+        viewType: RichOngoingNotificationViewType,
+    ): ContentViewInflationResult {
+        if (existingView is EnRouteView && !existingView.isReinflateNeeded())
+            return KeepExistingView
+        return when (viewType) {
+            RichOngoingNotificationViewType.Contracted -> {
+                val newView =
+                    LayoutInflater.from(systemUiContext)
+                        .inflate(
+                            R.layout.notification_template_en_route_contracted,
+                            parentView,
+                            /* attachToRoot= */ false
+                        ) as EnRouteView
+
+                InflatedContentViewHolder(newView) {
+                    EnRouteViewBinder.bindWhileAttached(newView, createViewModel())
+                }
+            }
+            RichOngoingNotificationViewType.Expanded,
+            RichOngoingNotificationViewType.HeadsUp -> NullContentView
+        }
+    }
+
+    private fun canKeepEnRouteView(
+        contentModel: EnRouteContentModel,
+        existingView: View?,
+        viewType: RichOngoingNotificationViewType
+    ): Boolean = true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
index 4705ace..72823a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.row.domain.interactor
 
 import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel
 import com.android.systemui.statusbar.notification.row.shared.TimerContentModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -26,4 +27,8 @@
     /** Content of a rich ongoing timer notification. */
     val timerContentModel: Flow<TimerContentModel> =
         repository.richOngoingContentModel.filterIsInstance<TimerContentModel>()
+
+    /** Content of a rich ongoing timer notification. */
+    val enRouteContentModel: Flow<EnRouteContentModel> =
+        repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
new file mode 100644
index 0000000..7e78cca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/EnRouteContentModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+/**
+ * Represents something en route.
+ *
+ * @param smallIcon the main small icon of the EnRoute notification.
+ * @param title the title of the EnRoute notification.
+ * @param text the text of the EnRoute notification.
+ */
+data class EnRouteContentModel(
+    val smallIcon: IconModel,
+    val title: CharSequence?,
+    val text: CharSequence?,
+) : RichOngoingContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
new file mode 100644
index 0000000..e5c2b5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/view/EnRouteView.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.view
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.internal.R
+import com.android.internal.widget.NotificationExpandButton
+
+class EnRouteView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+    private val configTracker = ConfigurationTracker(resources)
+
+    private lateinit var icon: ImageView
+    private lateinit var title: TextView
+    private lateinit var text: TextView
+    private lateinit var expandButton: NotificationExpandButton
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        icon = requireViewById(R.id.icon)
+        title = requireViewById(R.id.title)
+        text = requireViewById(R.id.text)
+
+        expandButton = requireViewById(R.id.expand_button)
+        expandButton.setExpanded(false)
+    }
+
+    /** the resources configuration has changed such that the view needs to be reinflated */
+    fun isReinflateNeeded(): Boolean = configTracker.hasUnhandledConfigChange()
+
+    fun setIcon(icon: Icon?) {
+        this.icon.setImageIcon(icon)
+    }
+
+    fun setTitle(title: CharSequence?) {
+        this.title.text = title
+    }
+
+    fun setText(text: CharSequence?) {
+        this.text.text = text
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
new file mode 100644
index 0000000..3b8957c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/EnRouteViewBinder.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [EnRouteView] to its [view model][EnRouteViewModel]. */
+object EnRouteViewBinder {
+    fun bindWhileAttached(
+        view: EnRouteView,
+        viewModel: EnRouteViewModel,
+    ): DisposableHandle {
+        return view.repeatWhenAttached { lifecycleScope.launch { bind(view, viewModel) } }
+    }
+
+    suspend fun bind(
+        view: EnRouteView,
+        viewModel: EnRouteViewModel,
+    ) = coroutineScope {
+        launch { viewModel.icon.collect { view.setIcon(it) } }
+        launch { viewModel.title.collect { view.setTitle(it) } }
+        launch { viewModel.text.collect { view.setText(it) } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.kt
new file mode 100644
index 0000000..307a983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModel.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.statusbar.notification.row.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.domain.interactor.NotificationRowInteractor
+import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+
+/** A view model for EnRoute notifications. */
+class EnRouteViewModel
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    rowInteractor: NotificationRowInteractor,
+) : FlowDumperImpl(dumpManager) {
+    init {
+        /* check if */ RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()
+    }
+
+    val icon: Flow<Icon?> = rowInteractor.enRouteContentModel.mapNotNull { it.smallIcon.icon }
+
+    val title: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.title }
+
+    val text: Flow<CharSequence?> = rowInteractor.enRouteContentModel.map { it.text }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
index dad52a3..5552d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/RichOngoingViewModelComponent.kt
@@ -33,4 +33,6 @@
     }
 
     fun createTimerViewModel(): TimerViewModel
+
+    fun createEnRouteViewModel(): EnRouteViewModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index a16129b..4be638f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -94,7 +94,6 @@
     private boolean mIsSmallScreen;
     private boolean mPulsing;
     private float mHideAmount;
-    private boolean mAppearing;
     private float mPulseHeight = MAX_PULSE_HEIGHT;
 
     /**
@@ -718,14 +717,6 @@
         return mHideAmount != 0;
     }
 
-    public void setAppearing(boolean appearing) {
-        mAppearing = appearing;
-    }
-
-    public boolean isAppearing() {
-        return mAppearing;
-    }
-
     public void setPulseHeight(float height) {
         if (height != mPulseHeight) {
             mPulseHeight = height;
@@ -856,7 +847,6 @@
         pw.println("mFractionToShade=" + mFractionToShade);
         pw.println("mHideAmount=" + mHideAmount);
         pw.println("mAppearFraction=" + mAppearFraction);
-        pw.println("mAppearing=" + mAppearing);
         pw.println("mExpansionFraction=" + mExpansionFraction);
         pw.println("mQsExpansionFraction=" + mQsExpansionFraction);
         pw.println("mExpandingVelocity=" + mExpandingVelocity);
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 d0c51bc2..1f767aa 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
@@ -114,7 +114,6 @@
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -1255,6 +1254,11 @@
     }
 
     @Override
+    public void closeGutsOnSceneTouch() {
+        mController.closeControlsDueToOutsideTouch();
+    }
+
+    @Override
     public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
         mScrollViewFields.setSyntheticScrollConsumer(consumer);
     }
@@ -1265,6 +1269,11 @@
     }
 
     @Override
+    public void setCurrentGestureInGutsConsumer(@Nullable Consumer<Boolean> consumer) {
+        mScrollViewFields.setCurrentGestureInGutsConsumer(consumer);
+    }
+
+    @Override
     public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
         mScrollViewFields.setHeadsUpHeightConsumer(consumer);
     }
@@ -1606,7 +1615,6 @@
         float translationY;
         float appearFraction = 1.0f;
         boolean appearing = calculateAppearFraction(height) < 1;
-        mAmbientState.setAppearing(appearing);
         if (!appearing) {
             translationY = 0;
             if (mShouldShowShelfOnly) {
@@ -2307,6 +2315,7 @@
 
     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
                                              boolean isRubberbanded) {
+        SceneContainerFlag.assertInLegacyMode();
         amount = Math.max(0, amount);
         if (animate) {
             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -3548,33 +3557,41 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
+        if (SceneContainerFlag.isEnabled()) {
             int action = ev.getActionMasked();
-            boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
-            if (mSendingTouchesToSceneFramework) {
-                MotionEvent adjustedEvent = MotionEvent.obtain(ev);
-                adjustedEvent.setLocation(ev.getRawX(), ev.getRawY());
-                mController.sendTouchToSceneFramework(adjustedEvent);
-                mScrollViewFields.sendCurrentGestureOverscroll(
-                        getExpandedInThisMotion() && !isUpOrCancel);
-                adjustedEvent.recycle();
-            } else if (!isUpOrCancel) {
-                // if this is the first touch being sent to the scene framework,
-                // convert it into a synthetic DOWN event.
-                mSendingTouchesToSceneFramework = true;
-                MotionEvent downEvent = MotionEvent.obtain(ev);
-                downEvent.setAction(MotionEvent.ACTION_DOWN);
-                downEvent.setLocation(ev.getRawX(), ev.getRawY());
-                mController.sendTouchToSceneFramework(downEvent);
-                mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion());
-                downEvent.recycle();
+            boolean isTouchInGuts = mController.isTouchInGutsView(ev);
+            if (action == MotionEvent.ACTION_DOWN && !isTouchInGuts) {
+                mController.closeControlsDueToOutsideTouch();
             }
+            if (mIsBeingDragged) {
+                boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+                if (mSendingTouchesToSceneFramework) {
+                    MotionEvent adjustedEvent = MotionEvent.obtain(ev);
+                    adjustedEvent.setLocation(ev.getRawX(), ev.getRawY());
+                    mScrollViewFields.sendCurrentGestureOverscroll(
+                            getExpandedInThisMotion() && !isUpOrCancel);
+                    mController.sendTouchToSceneFramework(adjustedEvent);
+                    adjustedEvent.recycle();
+                } else if (!isUpOrCancel) {
+                    // if this is the first touch being sent to the scene framework,
+                    // convert it into a synthetic DOWN event.
+                    mSendingTouchesToSceneFramework = true;
+                    MotionEvent downEvent = MotionEvent.obtain(ev);
+                    downEvent.setAction(MotionEvent.ACTION_DOWN);
+                    downEvent.setLocation(ev.getRawX(), ev.getRawY());
+                    mScrollViewFields.sendCurrentGestureInGuts(isTouchInGuts);
+                    mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion());
+                    mController.sendTouchToSceneFramework(downEvent);
+                    downEvent.recycle();
+                }
 
-            if (isUpOrCancel) {
-                mScrollViewFields.sendCurrentGestureOverscroll(false);
-                setIsBeingDragged(false);
+                if (isUpOrCancel) {
+                    mScrollViewFields.sendCurrentGestureInGuts(false);
+                    mScrollViewFields.sendCurrentGestureOverscroll(false);
+                    setIsBeingDragged(false);
+                }
+                return false;
             }
-            return false;
         }
         return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
     }
@@ -4286,7 +4303,7 @@
                 // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
                 // waiting for all child animations to finish.
                 // TODO(b/328390331) Do we need to reset this on QS expanded as well?
-                if (NotificationsHeadsUpRefactor.isEnabled()) {
+                if (SceneContainerFlag.isEnabled()) {
                     setHeadsUpAnimatingAway(false);
                 }
             } else {
@@ -4397,7 +4414,7 @@
 
     void onChildAnimationFinished() {
         setAnimationRunning(false);
-        if (NotificationsHeadsUpRefactor.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             setHeadsUpAnimatingAway(false);
         }
         requestChildrenUpdate();
@@ -4943,7 +4960,7 @@
     }
 
     public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
+        SceneContainerFlag.assertInLegacyMode();
         ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
         generateHeadsUpAnimation(row, isHeadsUp);
     }
@@ -4986,7 +5003,7 @@
             mNeedsAnimation = true;
             if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
                 row.setHeadsUpAnimatingAway(true);
-                if (NotificationsHeadsUpRefactor.isEnabled()) {
+                if (SceneContainerFlag.isEnabled()) {
                     setHeadsUpAnimatingAway(true);
                 }
             }
@@ -5180,7 +5197,7 @@
         updateClipping();
     }
 
-    /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
+    /** TODO(b/328390331) make this private, when {@link SceneContainerFlag} is removed */
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
         if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
             mHeadsUpAnimatingAway = headsUpAnimatingAway;
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 c25b30d..08d3e9f 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
@@ -127,7 +127,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
@@ -686,13 +685,13 @@
             new OnHeadsUpChangedListener() {
                 @Override
                 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
-                    NotificationsHeadsUpRefactor.assertInLegacyMode();
+                    SceneContainerFlag.assertInLegacyMode();
                     mView.setInHeadsUpPinnedMode(inPinnedMode);
                 }
 
                 @Override
                 public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-                    NotificationsHeadsUpRefactor.assertInLegacyMode();
+                    SceneContainerFlag.assertInLegacyMode();
                     NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
                     mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
                     generateHeadsUpAnimation(entry, isHeadsUp);
@@ -880,7 +879,7 @@
             });
         }
 
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         }
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -1508,7 +1507,7 @@
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
+        SceneContainerFlag.assertInLegacyMode();
         mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
     }
 
@@ -1690,7 +1689,7 @@
                 mVisibilityProvider.obtain(entry, true));
     }
 
-    public void closeControlsIfOutsideTouch(MotionEvent ev) {
+    private View getGutsView() {
         NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
         NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
         View translatingParentView = mSwipeHelper.getTranslatingParentView();
@@ -1703,15 +1702,35 @@
             // Checking menu
             view = translatingParentView;
         }
+        return view;
+    }
+
+    public void closeControlsIfOutsideTouch(MotionEvent ev) {
+        SceneContainerFlag.assertInLegacyMode();
+        View view = getGutsView();
         if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
             // Touch was outside visible guts / menu notification, close what's visible
-            mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
-                    false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
-                    false /* resetMenu */);
-            mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */);
+            closeAndSaveGuts();
         }
     }
 
+    void closeControlsDueToOutsideTouch() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        closeAndSaveGuts();
+    }
+
+    private void closeAndSaveGuts() {
+        mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
+                false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+                false /* resetMenu */);
+        mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */);
+    }
+
+    boolean isTouchInGutsView(MotionEvent event) {
+        View view = getGutsView();
+        return NotificationSwipeHelper.isTouchInView(event, view);
+    }
+
     public void clearSilentNotifications() {
         FooterViewRefactor.assertInLegacyMode();
         // Leave the shade open if there will be other notifs left over to clear
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 383d8b3..aa39539 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
@@ -53,6 +53,11 @@
      */
     var currentGestureOverscrollConsumer: Consumer<Boolean>? = null
     /**
+     * When a gesture is on open notification guts, which means scene container should not close the
+     * guts off of this gesture, we can notify the placeholder through here.
+     */
+    var currentGestureInGutsConsumer: Consumer<Boolean>? = null
+    /**
      * Any time the heads up height is recalculated, it should be updated here to be used by the
      * placeholder
      */
@@ -66,6 +71,10 @@
     fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) =
         currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll)
 
+    /** send [isCurrentGestureInGuts] to the [currentGestureInGutsConsumer], if present. */
+    fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
+        currentGestureInGutsConsumer?.accept(isCurrentGestureInGuts)
+
     /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
     fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
index f6d9351..4907d44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
@@ -39,4 +39,7 @@
      * consumed part of the gesture.
      */
     val isCurrentGestureOverscroll = MutableStateFlow(false)
+
+    /** Whether the current touch gesture is on any open notification guts. */
+    val isCurrentGestureInGuts = MutableStateFlow(false)
 }
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 8557afc..756cd87 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
@@ -18,6 +18,7 @@
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository
@@ -39,6 +40,7 @@
 constructor(
     private val viewHeightRepository: NotificationViewHeightRepository,
     private val placeholderRepository: NotificationPlaceholderRepository,
+    sceneInteractor: SceneInteractor,
     shadeInteractor: ShadeInteractor,
 ) {
     /** The bounds of the notification stack in the current scene. */
@@ -93,6 +95,15 @@
     val isCurrentGestureOverscroll: Flow<Boolean> =
         viewHeightRepository.isCurrentGestureOverscroll.asStateFlow()
 
+    /** Whether we should close any notification guts that are currently open. */
+    val shouldCloseGuts: Flow<Boolean> =
+        combine(
+            sceneInteractor.isSceneContainerUserInputOngoing,
+            viewHeightRepository.isCurrentGestureInGuts
+        ) { isUserInputOngoing, isCurrentGestureInGuts ->
+            isUserInputOngoing && !isCurrentGestureInGuts
+        }
+
     /** Sets the alpha to apply to the NSSL for the brightness mirror */
     fun setAlphaForBrightnessMirror(alpha: Float) {
         placeholderRepository.alphaForBrightnessMirror.value = alpha
@@ -119,6 +130,10 @@
         viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll
     }
 
+    fun setCurrentGestureInGuts(isInGuts: Boolean) {
+        viewHeightRepository.isCurrentGestureInGuts.value = isInGuts
+    }
+
     fun setConstrainedAvailableSpace(height: Int) {
         placeholderRepository.constrainedAvailableSpace.value = height
     }
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 1289cec..235b4da 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
@@ -71,6 +71,9 @@
     /** Set a consumer for current gesture overscroll events */
     fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?)
 
+    /** Set a consumer for current gesture in guts events */
+    fun setCurrentGestureInGutsConsumer(consumer: Consumer<Boolean>?)
+
     /** Set a consumer for heads up height changed events */
     fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
 
@@ -92,6 +95,12 @@
     /** Gets the inset for HUNs when they are not visible */
     fun getHeadsUpInset(): Int
 
+    /**
+     * Signals that any open Notification guts should be closed, as scene container is handling
+     * touch events.
+     */
+    fun closeGutsOnSceneTouch()
+
     /** Adds a listener to be notified, when the stack height might have changed. */
     fun addStackHeightChangedListener(runnable: Runnable)
 
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 5572f8e..d770b20 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
@@ -27,6 +27,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -36,7 +37,6 @@
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
 import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
@@ -93,7 +93,7 @@
 
         view.repeatWhenAttached {
             lifecycleScope.launch {
-                if (NotificationsHeadsUpRefactor.isEnabled) {
+                if (SceneContainerFlag.isEnabled) {
                     launch { hunBinder.bindHeadsUpNotifications(view) }
                 }
                 launch { bindShelf(shelf) }
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 c044f6f..6d5553f 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
@@ -36,6 +36,7 @@
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 /** Binds the [NotificationScrollView]. */
@@ -66,6 +67,7 @@
 
     suspend fun bind(): Nothing =
         view.asView().viewModel(
+            traceName = "NotificationScrollViewBinder",
             minWindowLifecycleState = WindowLifecycleState.ATTACHED,
             factory = viewModelFactory::create,
         ) { viewModel ->
@@ -98,13 +100,18 @@
                     .filter { it }
                     .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
             }
+            launch {
+                viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() }
+            }
 
             launchAndDispose {
                 view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
                 view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
+                view.setCurrentGestureInGutsConsumer(viewModel.currentGestureInGutsConsumer)
                 DisposableHandle {
                     view.setSyntheticScrollConsumer(null)
                     view.setCurrentGestureOverscrollConsumer(null)
+                    view.setCurrentGestureInGutsConsumer(null)
                 }
             }
         }
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 5fba615..e55492e6 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
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -26,7 +27,6 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
@@ -256,7 +256,7 @@
     }
 
     val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(null)
         } else {
             headsUpNotificationInteractor.topHeadsUpRow.dumpWhileCollecting("topHeadsUpRow")
@@ -264,7 +264,7 @@
     }
 
     val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(emptySet())
         } else {
             headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
@@ -272,7 +272,7 @@
     }
 
     val headsUpAnimationsEnabled: Flow<Boolean> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
             flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
@@ -280,7 +280,7 @@
     }
 
     val hasPinnedHeadsUpRow: Flow<Boolean> by lazy {
-        if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
             headsUpNotificationInteractor.hasPinnedRows.dumpWhileCollecting("hasPinnedHeadsUpRow")
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 0e984cf..3e42413 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
@@ -17,11 +17,13 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -60,42 +62,46 @@
     keyguardInteractor: Lazy<KeyguardInteractor>,
 ) :
     ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
-    SysUiViewModel() {
+    ExclusiveActivatable() {
 
     override suspend fun onActivated(): Nothing {
         activateFlowDumper()
     }
 
-    private fun expandFractionForScene(scene: SceneKey, shadeExpansion: Float): Float =
-        when (scene) {
+    private fun expandedInScene(scene: SceneKey): Boolean {
+        return when (scene) {
             Scenes.Lockscreen,
-            Scenes.QuickSettings -> 1f
-            else -> shadeExpansion
+            Scenes.Shade,
+            Scenes.QuickSettings -> true
+            else -> false
         }
+    }
 
-    private fun expandFractionForTransition(
-        state: ObservableTransitionState.Transition.ChangeCurrentScene,
+    private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
+        // The lockscreen stack is visible during all transitions away from the lockscreen, so keep
+        // the stack expanded until those transitions finish.
+        return (expandedInScene(change.fromScene) && expandedInScene(change.toScene)) ||
+            change.isBetween({ it == Scenes.Lockscreen }, { true })
+    }
+
+    private fun expandFractionDuringSceneChange(
+        change: ChangeScene,
         shadeExpansion: Float,
-        shadeMode: ShadeMode,
         qsExpansion: Float,
-        quickSettingsScene: SceneKey
-    ): Float =
-        if (
-            state.isBetween({ it == Scenes.Lockscreen }, { it in SceneFamilies.NotifShade }) ||
-                state.isBetween({ it in SceneFamilies.NotifShade }, { it == quickSettingsScene })
-        ) {
+    ): Float {
+        return if (fullyExpandedDuringSceneChange(change)) {
             1f
-        } else if (
-            shadeMode != ShadeMode.Split &&
-                state.isBetween({ it in SceneFamilies.Home }, { it == quickSettingsScene })
-        ) {
+        } else if (change.isBetween({ it == Scenes.Gone }, { it in SceneFamilies.NotifShade })) {
+            shadeExpansion
+        } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
             // during QS expansion, increase fraction at same rate as scrim alpha,
             // but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
             (qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
                 .coerceIn(0f, 1f)
         } else {
-            shadeExpansion
+            0f
         }
+    }
 
     /**
      * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
@@ -109,21 +115,17 @@
                 shadeInteractor.qsExpansion,
                 sceneInteractor.transitionState,
                 sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
-            ) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene ->
+            ) { shadeExpansion, _, qsExpansion, transitionState, _ ->
                 when (transitionState) {
-                    is ObservableTransitionState.Idle ->
-                        expandFractionForScene(transitionState.currentScene, shadeExpansion)
-                    is ObservableTransitionState.Transition.ChangeCurrentScene ->
-                        expandFractionForTransition(
+                    is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
+                    is ChangeScene ->
+                        expandFractionDuringSceneChange(
                             transitionState,
                             shadeExpansion,
-                            shadeMode,
                             qsExpansion,
-                            quickSettingsScene
                         )
-                    is ObservableTransitionState.Transition.ShowOrHideOverlay,
-                    is ObservableTransitionState.Transition.ReplaceOverlay ->
-                        TODO("b/359173565: Handle overlay transitions")
+                    is Transition.ShowOrHideOverlay,
+                    is Transition.ReplaceOverlay -> TODO("b/359173565: Handle overlay transitions")
                 }
             }
             .distinctUntilChanged()
@@ -132,11 +134,12 @@
     val qsExpandFraction: Flow<Float> =
         shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
 
+    /** Whether we should close any open notification guts. */
+    val shouldCloseGuts: Flow<Boolean> = stackAppearanceInteractor.shouldCloseGuts
+
     val shouldResetStackTop: Flow<Boolean> =
         sceneInteractor.transitionState
-            .mapNotNull { state ->
-                state is ObservableTransitionState.Idle && state.currentScene == Scenes.Gone
-            }
+            .mapNotNull { state -> state is Idle && state.currentScene == Scenes.Gone }
             .distinctUntilChanged()
             .dumpWhileCollecting("shouldResetStackTop")
 
@@ -200,6 +203,10 @@
     val currentGestureOverscrollConsumer: (Boolean) -> Unit =
         stackAppearanceInteractor::setCurrentGestureOverscroll
 
+    /** Receives whether the current touch gesture is inside any open guts. */
+    val currentGestureInGutsConsumer: (Boolean) -> Unit =
+        stackAppearanceInteractor::setCurrentGestureInGuts
+
     /** Whether the notification stack is scrollable or not. */
     val isScrollable: Flow<Boolean> =
         sceneInteractor.currentScene
@@ -241,7 +248,7 @@
     }
 }
 
-private fun ObservableTransitionState.Transition.ChangeCurrentScene.isBetween(
+private fun ChangeScene.isBetween(
     a: (SceneKey) -> Boolean,
-    b: (SceneKey) -> Boolean
+    b: (SceneKey) -> Boolean,
 ): Boolean = (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
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 ffa1de7..69c1bf3 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
@@ -20,7 +20,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -52,7 +52,7 @@
     featureFlags: FeatureFlagsClassic,
     dumpManager: DumpManager,
 ) :
-    SysUiViewModel(),
+    ExclusiveActivatable(),
     ActivatableFlowDumper by ActivatableFlowDumperImpl(
         dumpManager = dumpManager,
         tag = "NotificationsPlaceholderViewModel",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f63ee7b..aed00d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,6 +20,7 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import androidx.annotation.VisibleForTesting
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -141,10 +142,6 @@
     private val communalSceneInteractor: CommunalSceneInteractor,
     unfoldTransitionInteractor: UnfoldTransitionInteractor,
 ) : FlowDumperImpl(dumpManager) {
-    // TODO(b/349784682): Transform deprecated states for Flexiglass
-    private val statesForConstrainedNotifications: Set<KeyguardState> =
-        setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
-    private val statesForHiddenKeyguard: Set<KeyguardState> = setOf(GONE, OCCLUDED)
 
     /**
      * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
@@ -217,14 +214,16 @@
 
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
-        combine(
-                keyguardTransitionInteractor.finishedKeyguardState.map {
-                    statesForConstrainedNotifications.contains(it)
-                },
+        anyOf(
+                keyguardTransitionInteractor.isFinishedIn(AOD),
+                keyguardTransitionInteractor.isFinishedIn(DOZING),
+                keyguardTransitionInteractor.isFinishedIn(ALTERNATE_BOUNCER),
+                keyguardTransitionInteractor.isFinishedIn(
+                    scene = Scenes.Bouncer,
+                    stateWithoutSceneContainer = PRIMARY_BOUNCER
+                ),
                 keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
-            ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
-                constrainedNotificationState || transitioningToOrFromLockscreen
-            }
+            )
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -250,9 +249,10 @@
     /** If the user is visually on the glanceable hub or transitioning to/from it */
     private val isOnGlanceableHub: Flow<Boolean> =
         combine(
-                keyguardTransitionInteractor.finishedKeyguardState.map { state ->
-                    state == GLANCEABLE_HUB
-                },
+                keyguardTransitionInteractor.isFinishedIn(
+                    scene = Scenes.Communal,
+                    stateWithoutSceneContainer = GLANCEABLE_HUB
+                ),
                 anyOf(
                     keyguardTransitionInteractor.isInTransition(
                         edge = Edge.create(to = Scenes.Communal),
@@ -424,32 +424,19 @@
             .onStart { emit(1f) }
             .dumpWhileCollecting("alphaForShadeAndQsExpansion")
 
-    private fun toFlowArray(
-        states: Set<KeyguardState>,
-        flow: (KeyguardState) -> Flow<Boolean>
-    ): Array<Flow<Boolean>> {
-        return states.map { flow(it) }.toTypedArray()
-    }
-
     private val isTransitioningToHiddenKeyguard: Flow<Boolean> =
         flow {
                 while (currentCoroutineContext().isActive) {
                     emit(false)
                     // Ensure states are inactive to start
-                    allOf(
-                            *toFlowArray(statesForHiddenKeyguard) { state ->
-                                keyguardTransitionInteractor.transitionValue(state).map { it == 0f }
-                            }
-                        )
-                        .first { it }
+                    allOf(isNotOnState(OCCLUDED), isNotOnState(GONE, Scenes.Gone)).first { it }
                     // Wait for a qualifying transition to begin
                     anyOf(
-                            *toFlowArray(statesForHiddenKeyguard) { state ->
-                                keyguardTransitionInteractor
-                                    .transition(Edge.create(to = state))
-                                    .map { it.value > 0f && it.transitionState == RUNNING }
-                                    .onStart { emit(false) }
-                            }
+                            transitionToIsRunning(Edge.create(to = OCCLUDED)),
+                            transitionToIsRunning(
+                                edge = Edge.create(to = Scenes.Gone),
+                                edgeWithoutSceneContainer = Edge.create(to = GONE)
+                            )
                         )
                         .first { it }
                     emit(true)
@@ -458,13 +445,7 @@
                     // it is considered safe to reset alpha to 1f for HUNs.
                     combine(
                             keyguardInteractor.statusBarState,
-                            allOf(
-                                *toFlowArray(statesForHiddenKeyguard) { state ->
-                                    keyguardTransitionInteractor.transitionValue(state).map {
-                                        it == 0f
-                                    }
-                                }
-                            )
+                            allOf(isNotOnState(OCCLUDED), isNotOnState(GONE, Scenes.Gone))
                         ) { statusBarState, stateIsReversed ->
                             statusBarState == SHADE || stateIsReversed
                         }
@@ -473,6 +454,17 @@
             }
             .dumpWhileCollecting("isTransitioningToHiddenKeyguard")
 
+    private fun isNotOnState(stateWithoutSceneContainer: KeyguardState, scene: SceneKey? = null) =
+        keyguardTransitionInteractor
+            .transitionValue(scene = scene, stateWithoutSceneContainer = stateWithoutSceneContainer)
+            .map { it == 0f }
+
+    private fun transitionToIsRunning(edge: Edge, edgeWithoutSceneContainer: Edge? = null) =
+        keyguardTransitionInteractor
+            .transition(edge = edge, edgeWithoutSceneContainer = edgeWithoutSceneContainer)
+            .map { it.value > 0f && it.transitionState == RUNNING }
+            .onStart { emit(false) }
+
     val panelAlpha = keyguardInteractor.panelAlpha
 
     private fun bouncerToGoneNotificationAlpha(viewState: ViewStateAccessor): Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index e3242d1..7227b93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.Flags.lightRevealMigration;
 import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
+import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -870,7 +871,10 @@
         mBubblesOptional.ifPresent(this::initBubbles);
         mKeyguardBypassController.listenForQsExpandedChange();
 
-        mStatusBarSignalPolicy.init();
+        if (!statusBarSignalPolicyRefactor()) {
+            mStatusBarSignalPolicy.init();
+        }
+
         mKeyguardIndicationController.init();
 
         mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ea28a7..1efad3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -47,7 +47,6 @@
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AnimationStateHandler;
 import com.android.systemui.statusbar.policy.AvalancheController;
@@ -284,7 +283,7 @@
     private void onShadeOrQsExpanded(Boolean isExpanded) {
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
-            if (!NotificationsHeadsUpRefactor.isEnabled() && isExpanded) {
+            if (!SceneContainerFlag.isEnabled() && isExpanded) {
                 mHeadsUpAnimatingAway.setValue(false);
             }
         }
@@ -517,7 +516,7 @@
 
     @Nullable
     private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
-        if (NotificationsHeadsUpRefactor.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             return (HeadsUpEntryPhone) mTopHeadsUpRow.getValue();
         } else {
             return (HeadsUpEntryPhone) getTopHeadsUpEntry();
@@ -711,7 +710,7 @@
         }
 
         private NotificationEntry requireEntry() {
-            /* check if */ NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode();
+            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
             return Objects.requireNonNull(mEntry);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 3ba62b1..05bd1a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -44,6 +44,7 @@
 
 import androidx.lifecycle.Observer;
 
+import com.android.settingslib.notification.modes.ZenMode;
 import com.android.systemui.Flags;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -78,6 +79,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.DateFormatUtil;
@@ -99,7 +101,6 @@
                 CommandQueue.Callbacks,
                 RotationLockControllerCallback,
                 Listener,
-                ZenModeController.Callback,
                 DeviceProvisionedListener,
                 KeyguardStateController.Callback,
                 PrivacyItemController.Callback,
@@ -161,6 +162,7 @@
     private final RecordingController mRecordingController;
     private final RingerModeTracker mRingerModeTracker;
     private final PrivacyLogger mPrivacyLogger;
+    private final ZenModeInteractor mZenModeInteractor;
 
     private boolean mZenVisible;
     private boolean mVibrateVisible;
@@ -193,6 +195,7 @@
             PrivacyItemController privacyItemController,
             PrivacyLogger privacyLogger,
             ConnectedDisplayInteractor connectedDisplayInteractor,
+            ZenModeInteractor zenModeInteractor,
             JavaAdapter javaAdapter
     ) {
         mIconController = iconController;
@@ -224,6 +227,7 @@
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
         mPrivacyLogger = privacyLogger;
+        mZenModeInteractor = zenModeInteractor;
         mJavaAdapter = javaAdapter;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
@@ -355,7 +359,13 @@
         mBluetooth.addCallback(this);
         mProvisionedController.addCallback(this);
         mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
-        mZenController.addCallback(this);
+        if (usesModeIcons()) {
+            // Note that we're not fully replacing ZenModeController with ZenModeInteractor, so
+            // we listen for the extra event here but still add the ZMC callback.
+            mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(),
+                    this::onActiveModeChanged);
+        }
+        mZenController.addCallback(mZenControllerCallback);
         if (!Flags.statusBarScreenSharingChips()) {
             // If the flag is enabled, the cast icon is handled in the new screen sharing chips
             // instead of here so we don't need to listen for events here.
@@ -385,15 +395,40 @@
                 () -> mResources.getString(R.string.accessibility_managed_profile));
     }
 
-    @Override
-    public void onZenChanged(int zen) {
-        updateVolumeZen();
+    private void onActiveModeChanged(@Nullable ZenMode mode) {
+        if (!usesModeIcons()) {
+            Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled");
+            return;
+        }
+        boolean visible = mode != null;
+        if (visible) {
+            // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode;
+            //  this is a shortcut for testing.
+            String resPackage = mode.getIconKey().resPackage();
+            int iconResId = mode.getIconKey().resId();
+
+            mIconController.setResourceIcon(mSlotZen, resPackage, iconResId,
+                    /* preloadedIcon= */ null, mode.getName());
+        }
+        if (visible != mZenVisible) {
+            mIconController.setIconVisibility(mSlotZen, visible);
+            mZenVisible = visible;
+        }
     }
 
-    @Override
-    public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
-        updateVolumeZen();
-    }
+    // TODO: b/308591859 - Should be removed and use the ZenModeInteractor only.
+    private final ZenModeController.Callback mZenControllerCallback =
+            new ZenModeController.Callback() {
+                @Override
+                public void onZenChanged(int zen) {
+                    updateVolumeZen();
+                }
+
+                @Override
+                public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+                    updateVolumeZen();
+                }
+            };
 
     private void updateAlarm() {
         final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
@@ -417,15 +452,24 @@
         return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString);
     }
 
-    private final void updateVolumeZen() {
+    private void updateVolumeZen() {
+        int zen = mZenController.getZen();
+        if (!usesModeIcons()) {
+            updateZenIcon(zen);
+        }
+        updateRingerAndAlarmIcons(zen);
+    }
+
+    private void updateZenIcon(int zen) {
+        if (usesModeIcons()) {
+            Log.wtf(TAG, "updateZenIcon shouldn't be called if MODES_UI_ICONS is enabled");
+            return;
+        }
+
         boolean zenVisible = false;
         int zenIconId = 0;
         String zenDescription = null;
 
-        boolean vibrateVisible = false;
-        boolean muteVisible = false;
-        int zen = mZenController.getZen();
-
         if (DndTile.isVisible(mSharedPreferences) || DndTile.isCombinedIcon(mSharedPreferences)) {
             zenVisible = zen != Global.ZEN_MODE_OFF;
             zenIconId = R.drawable.stat_sys_dnd;
@@ -440,7 +484,21 @@
             zenDescription = mResources.getString(R.string.interruption_level_priority);
         }
 
-        if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) {
+        if (zenVisible) {
+            mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
+        }
+        if (zenVisible != mZenVisible) {
+            mIconController.setIconVisibility(mSlotZen, zenVisible);
+            mZenVisible = zenVisible;
+        }
+    }
+
+    private void updateRingerAndAlarmIcons(int zen) {
+        boolean vibrateVisible = false;
+        boolean muteVisible = false;
+
+        NotificationManager.Policy consolidatedPolicy = mZenController.getConsolidatedPolicy();
+        if (!ZenModeConfig.isZenOverridingRinger(zen, consolidatedPolicy)) {
             final Integer ringerModeInternal =
                     mRingerModeTracker.getRingerModeInternal().getValue();
             if (ringerModeInternal != null) {
@@ -452,14 +510,6 @@
             }
         }
 
-        if (zenVisible) {
-            mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
-        }
-        if (zenVisible != mZenVisible) {
-            mIconController.setIconVisibility(mSlotZen, zenVisible);
-            mZenVisible = zenVisible;
-        }
-
         if (vibrateVisible != mVibrateVisible) {
             mIconController.setIconVisibility(mSlotVibrate, vibrateVisible);
             mVibrateVisible = vibrateVisible;
@@ -888,4 +938,9 @@
 
         mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
     }
+
+    private static boolean usesModeIcons() {
+        return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+                && android.app.Flags.modesUiIcons();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index da5877b..8f2d4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -19,12 +19,12 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -112,7 +112,7 @@
     }
 
     private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f93ff2..43f9af6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,12 +68,11 @@
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
 import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.KeyguardDone;
@@ -104,6 +103,7 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dagger.Lazy;
@@ -170,6 +170,7 @@
     private final Lazy<ShadeController> mShadeController;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
     private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy;
+    private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     private Job mListenForAlternateBouncerTransitionSteps = null;
     private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
@@ -179,6 +180,8 @@
     private float mFraction = -1f;
     private boolean mTracking = false;
     private boolean mBouncerShowingOverDream;
+    private int mAttemptsToShowBouncer = 0;
+    private DelayableExecutor mExecutor;
 
     private final PrimaryBouncerExpansionCallback mExpansionCallback =
             new PrimaryBouncerExpansionCallback() {
@@ -315,8 +318,6 @@
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
 
-    private FeatureFlags mFlags;
-
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@@ -360,8 +361,6 @@
             }
         }
     };
-    private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
-    private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
     private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
     private final JavaAdapter mJavaAdapter;
     private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
@@ -391,17 +390,19 @@
             UdfpsOverlayInteractor udfpsOverlayInteractor,
             ActivityStarter activityStarter,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
             Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
             SelectedUserInteractor selectedUserInteractor,
-            Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
             JavaAdapter javaAdapter,
             Lazy<SceneInteractor> sceneInteractorLazy,
             StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor,
-            Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy
+            @Main DelayableExecutor executor,
+            Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
+            DismissCallbackRegistry dismissCallbackRegistry
     ) {
         mContext = context;
+        mExecutor = executor;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
         mConfigurationController = configurationController;
@@ -426,18 +427,19 @@
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
         mMainDispatcher = mainDispatcher;
-        mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
         mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
         mSelectedUserInteractor = selectedUserInteractor;
-        mSurfaceBehindInteractor = surfaceBehindInteractor;
         mJavaAdapter = javaAdapter;
         mSceneInteractorLazy = sceneInteractorLazy;
         mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
         mDeviceEntryInteractorLazy = deviceEntryInteractorLazy;
+        mDismissCallbackRegistry = dismissCallbackRegistry;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    KeyguardDismissTransitionInteractor mKeyguardDismissTransitionInteractor;
     CoroutineDispatcher mMainDispatcher;
 
     @Override
@@ -711,13 +713,7 @@
      * {@link #needsFullscreenBouncer()}.
      */
     protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
-        boolean isDozing = mDozing;
-        if (Flags.simPinRaceConditionOnRestart()) {
-            KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
-                    .getTo();
-            isDozing = mDozing || toState == KeyguardState.DOZING || toState == KeyguardState.AOD;
-        }
-        if (needsFullscreenBouncer() && !isDozing) {
+        if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
             if (!primaryBouncerIsShowing()) {
                 if (SceneContainerFlag.isEnabled()) {
@@ -727,9 +723,22 @@
                 } else {
                     if (Flags.simPinRaceConditionOnRestart()) {
                         if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) {
+                            mAttemptsToShowBouncer = 0;
                             mCentralSurfaces.hideKeyguard();
                         } else {
-                            mCentralSurfaces.showKeyguard();
+                            if (mAttemptsToShowBouncer > 6) {
+                                mAttemptsToShowBouncer = 0;
+                                Log.e(TAG, "Too many failed attempts to show bouncer, showing "
+                                        + "keyguard instead");
+                                mCentralSurfaces.showKeyguard();
+                            } else {
+                                Log.v(TAG, "Failed to show bouncer, attempt #: "
+                                        + mAttemptsToShowBouncer++);
+                                mExecutor.executeDelayed(() ->
+                                        showBouncerOrKeyguard(hideBouncerWhenShowing,
+                                            isFalsingReset),
+                                        500);
+                            }
                         }
                     } else {
                         mCentralSurfaces.hideKeyguard();
@@ -985,6 +994,8 @@
             }
             if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) {
                 hideAlternateBouncer(true);
+                mDismissCallbackRegistry.notifyDismissCancelled();
+                mPrimaryBouncerInteractor.setDismissAction(null, null);
             }
             mKeyguardUpdateManager.sendKeyguardReset();
             updateStates();
@@ -1600,7 +1611,7 @@
         }
 
         if (KeyguardWmStateRefactor.isEnabled()) {
-            mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+            mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition(
                     "SBKVM#keyguardAuthenticated");
         }
     }
@@ -1874,6 +1885,11 @@
                 || mode == KeyguardSecurityModel.SecurityMode.SimPuk;
     }
 
+    @VisibleForTesting
+    void setAttemptsToShowBouncer(int attempts) {
+        mAttemptsToShowBouncer = attempts;
+    }
+
     /**
      * Delegate used to send show and hide events to an alternate authentication method instead of
      * the regular pin/pattern/password bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index ba59398..d5fafe2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
@@ -23,16 +25,19 @@
 import android.util.Log;
 
 import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -40,10 +45,13 @@
 
 import javax.inject.Inject;
 
-/** Controls the signal policies for icons shown in the statusbar. **/
+/** Controls the signal policies for icons shown in the statusbar. */
 @SysUISingleton
-public class StatusBarSignalPolicy implements SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable {
+public class StatusBarSignalPolicy
+        implements SignalCallback,
+                SecurityController.SecurityControllerCallback,
+                Tunable,
+                CoreStartable {
     private static final String TAG = "StatusBarSignalPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -61,16 +69,15 @@
     private final Handler mHandler = Handler.getMain();
     private final CarrierConfigTracker mCarrierConfigTracker;
     private final TunerService mTunerService;
+    private final JavaAdapter mJavaAdapter;
+    private final AirplaneModeInteractor mAirplaneModeInteractor;
 
     private boolean mHideAirplane;
     private boolean mHideMobile;
     private boolean mHideEthernet;
-    private boolean mActivityEnabled;
+    private final boolean mActivityEnabled;
 
-    // Track as little state as possible, and only for padding purposes
-    private boolean mIsAirplaneMode = false;
-
-    private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
+    private final ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
     private boolean mInitialized;
 
     @Inject
@@ -80,15 +87,19 @@
             CarrierConfigTracker carrierConfigTracker,
             NetworkController networkController,
             SecurityController securityController,
-            TunerService tunerService
+            TunerService tunerService,
+            JavaAdapter javaAdapter,
+            AirplaneModeInteractor airplaneModeInteractor
     ) {
         mContext = context;
 
         mIconController = iconController;
         mCarrierConfigTracker = carrierConfigTracker;
+        mJavaAdapter = javaAdapter;
         mNetworkController = networkController;
         mSecurityController = securityController;
         mTunerService = tunerService;
+        mAirplaneModeInteractor = airplaneModeInteractor;
 
         mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
         mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
@@ -100,15 +111,35 @@
         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
     }
 
+    @Override
+    public void start() {
+        if (!statusBarSignalPolicyRefactor()) {
+            return;
+        }
+
+        mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
+        mNetworkController.addCallback(this);
+        mSecurityController.addCallback(this);
+
+        mJavaAdapter.alwaysCollectFlow(
+                mAirplaneModeInteractor.isAirplaneMode(), this::updateAirplaneModeIcon);
+    }
+
     /** Call to initialize and register this class with the system. */
     public void init() {
-        if (mInitialized) {
+        if (mInitialized || statusBarSignalPolicyRefactor()) {
             return;
         }
         mInitialized = true;
         mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
         mNetworkController.addCallback(this);
         mSecurityController.addCallback(this);
+
+        if (statusBarSignalPolicyRefactor()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mAirplaneModeInteractor.isAirplaneMode(),
+                    this::updateAirplaneModeIcon);
+        }
     }
 
     public void destroy() {
@@ -222,15 +253,19 @@
 
     @Override
     public void setIsAirplaneMode(IconState icon) {
+        if (statusBarSignalPolicyRefactor()) {
+            return;
+        }
+
         if (DEBUG) {
             Log.d(TAG, "setIsAirplaneMode: "
                     + "icon = " + (icon == null ? "" : icon.toString()));
         }
-        mIsAirplaneMode = icon.visible && !mHideAirplane;
+        boolean isAirplaneMode = icon.visible && !mHideAirplane;
         int resId = icon.icon;
         String description = icon.contentDescription;
 
-        if (mIsAirplaneMode && resId > 0) {
+        if (isAirplaneMode && resId > 0) {
             mIconController.setIcon(mSlotAirplane, resId, description);
             mIconController.setIconVisibility(mSlotAirplane, true);
         } else {
@@ -238,6 +273,21 @@
         }
     }
 
+    public void updateAirplaneModeIcon(boolean isAirplaneModeOn) {
+        if (StatusBarSignalPolicyRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        boolean isAirplaneMode = isAirplaneModeOn && !mHideAirplane;
+        mIconController.setIconVisibility(mSlotAirplane, isAirplaneMode);
+        if (isAirplaneMode) {
+            mIconController.setIcon(
+                    mSlotAirplane,
+                    TelephonyIcons.FLIGHT_MODE_ICON,
+                    mContext.getString(R.string.accessibility_airplane_mode));
+        }
+    }
+
     /**
      * Stores the statusbar state for no Calling & SMS.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
index 62641fe..0577f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shared
+package com.android.systemui.statusbar.phone
 
 import com.android.systemui.Flags
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
-/** Helper for reading or using the notifications heads up refactor flag state. */
+/** Helper for reading or using the status_bar_signal_policy_refactor flag state. */
 @Suppress("NOTHING_TO_INLINE")
-object NotificationsHeadsUpRefactor {
+object StatusBarSignalPolicyRefactor {
     /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationsHeadsUpRefactor()
+        get() = Flags.statusBarSignalPolicyRefactor()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
index 1ada30e..ee528e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java
@@ -18,9 +18,12 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.ArraySet;
 
+import androidx.annotation.DrawableRes;
+
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
@@ -58,6 +61,18 @@
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
 
     /**
+     * Adds or updates an icon for the given slot.
+     *
+     * @param resPackage the package name containing the resource in question. Can be null if the
+     *      icon is a system icon (e.g. a resource from {@code android.R.drawable} or
+     *      {@code com.android.internal.R.drawable}).
+     * @param iconResId id of the drawable resource
+     * @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known
+     */
+    void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId,
+            @Nullable Drawable preloadedIcon, CharSequence contentDescription);
+
+    /**
      * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
      * set up (inflated and added to the view hierarchy).
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
index 85213cb..ad3a9e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java
@@ -18,16 +18,22 @@
 
 import static com.android.systemui.statusbar.phone.ui.StatusBarIconList.Slot;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.ViewGroup;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.StatusBarIcon;
@@ -221,19 +227,66 @@
         }
     }
 
-    /** */
     @Override
     public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
+        setResourceIconInternal(
+                slot,
+                Icon.createWithResource(mContext, resourceId),
+                /* preloadedIcon= */ null,
+                contentDescription,
+                StatusBarIcon.Type.SystemIcon);
+    }
+
+    @Override
+    public void setResourceIcon(String slot, @Nullable String resPackage,
+            @DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
+            CharSequence contentDescription) {
+        if (!usesModeIcons()) {
+            Log.wtf("TAG",
+                    "StatusBarIconController.setResourceIcon() should not be called without "
+                            + "MODES_UI & MODES_UI_ICONS!");
+            // Fall back to old implementation, although it will not load the icon if it's from a
+            // different package.
+            setIcon(slot, iconResId, contentDescription);
+            return;
+        }
+
+        Icon icon = resPackage != null
+                ? Icon.createWithResource(resPackage, iconResId)
+                : Icon.createWithResource(mContext, iconResId);
+
+        setResourceIconInternal(
+                slot,
+                icon,
+                preloadedIcon,
+                contentDescription,
+                StatusBarIcon.Type.ResourceIcon);
+    }
+
+    private void setResourceIconInternal(String slot, Icon resourceIcon,
+            @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+            StatusBarIcon.Type type) {
+        checkArgument(resourceIcon.getType() == Icon.TYPE_RESOURCE,
+                "Expected Icon of TYPE_RESOURCE, but got " + resourceIcon.getType());
+        String resPackage = resourceIcon.getResPackage();
+        if (TextUtils.isEmpty(resPackage)) {
+            resPackage = mContext.getPackageName();
+        }
+
         StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
         if (holder == null) {
-            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
-                    Icon.createWithResource(mContext, resourceId), 0, 0,
-                    contentDescription, StatusBarIcon.Type.SystemIcon);
+            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, resPackage,
+                    resourceIcon, /* iconLevel= */ 0, /* number=*/ 0,
+                    contentDescription, type);
+            icon.preloadedIcon = preloadedIcon;
             holder = StatusBarIconHolder.fromIcon(icon);
             setIcon(slot, holder);
         } else {
-            holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
+            holder.getIcon().pkg = resPackage;
+            holder.getIcon().icon = resourceIcon;
             holder.getIcon().contentDescription = contentDescription;
+            holder.getIcon().type = type;
+            holder.getIcon().preloadedIcon = preloadedIcon;
             handleSet(slot, holder);
         }
     }
@@ -524,4 +577,9 @@
             return slot + EXTERNAL_SLOT_SUFFIX;
         }
     }
+
+    private static boolean usesModeIcons() {
+        return android.app.Flags.modesApi() && android.app.Flags.modesUi()
+                && android.app.Flags.modesUiIcons();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 1a55f7d..f5cfc8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -31,7 +31,7 @@
 import androidx.annotation.ArrayRes
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -252,7 +253,10 @@
             }
             // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
             // so skip the underlying network check if it's not CELLULAR.
-            if (!this.hasTransport(TRANSPORT_CELLULAR)) {
+            if (
+                !this.hasTransport(TRANSPORT_CELLULAR) &&
+                    !Flags.statusBarAlwaysCheckUnderlyingNetworks()
+            ) {
                 return mainWifiInfo
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index d46aaf4..c24d694 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -124,10 +125,6 @@
 
                                     // Colors
                                     val textColor = chipModel.colors.text(chipContext)
-                                    chipDefaultIconView.imageTintList =
-                                        ColorStateList.valueOf(textColor)
-                                    chipBackgroundView.getCustomIconView()?.imageTintList =
-                                        ColorStateList.valueOf(textColor)
                                     chipTimeView.setTextColor(textColor)
                                     chipTextView.setTextColor(textColor)
                                     (chipBackgroundView.background as GradientDrawable).color =
@@ -173,13 +170,22 @@
         // it.
         backgroundView.removeView(backgroundView.getCustomIconView())
 
+        val iconTint = chipModel.colors.text(defaultIconView.context)
+
         when (val icon = chipModel.icon) {
             null -> {
                 defaultIconView.visibility = View.GONE
             }
-            is OngoingActivityChipModel.ChipIcon.Basic -> {
+            is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
                 IconViewBinder.bind(icon.impl, defaultIconView)
                 defaultIconView.visibility = View.VISIBLE
+                defaultIconView.tintView(iconTint)
+            }
+            is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> {
+                StatusBarRonChips.assertInNewMode()
+                IconViewBinder.bind(icon.impl, defaultIconView)
+                defaultIconView.visibility = View.VISIBLE
+                defaultIconView.untintView()
             }
             is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
                 // Hide the default icon since we'll show this custom icon instead.
@@ -194,6 +200,7 @@
                     // maybe include the app name.
                     contentDescription =
                         context.resources.getString(R.string.ongoing_phone_call_content_description)
+                    tintView(iconTint)
                 }
 
                 // 2. If we just reinflated the view, we may need to detach the icon view from the
@@ -219,6 +226,14 @@
         return this.findViewById(CUSTOM_ICON_VIEW_ID)
     }
 
+    private fun ImageView.tintView(color: Int) {
+        this.imageTintList = ColorStateList.valueOf(color)
+    }
+
+    private fun ImageView.untintView() {
+        this.imageTintList = null
+    }
+
     private fun generateCustomIconLayoutParams(iconView: ImageView): FrameLayout.LayoutParams {
         val customIconSize =
             iconView.context.resources.getDimensionPixelSize(
@@ -237,10 +252,13 @@
                 chipTextView.text = chipModel.secondsUntilStarted.toString()
                 chipTextView.visibility = View.VISIBLE
 
-                // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
-                // [Chronometer.start].
-                chipTimeView.stop()
-                chipTimeView.visibility = View.GONE
+                chipTimeView.hide()
+            }
+            is OngoingActivityChipModel.Shown.Text -> {
+                chipTextView.text = chipModel.text
+                chipTextView.visibility = View.VISIBLE
+
+                chipTimeView.hide()
             }
             is OngoingActivityChipModel.Shown.Timer -> {
                 ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
@@ -250,14 +268,18 @@
             }
             is OngoingActivityChipModel.Shown.IconOnly -> {
                 chipTextView.visibility = View.GONE
-                // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
-                // [Chronometer.start].
-                chipTimeView.stop()
-                chipTimeView.visibility = View.GONE
+                chipTimeView.hide()
             }
         }
     }
 
+    private fun ChipChronometer.hide() {
+        // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+        // [Chronometer.start].
+        this.stop()
+        this.visibility = View.GONE
+    }
+
     private fun updateChipPadding(
         chipModel: OngoingActivityChipModel.Shown,
         backgroundView: View,
@@ -356,6 +378,7 @@
                 chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
             }
             is OngoingActivityChipModel.Shown.Timer,
+            is OngoingActivityChipModel.Shown.Text,
             is OngoingActivityChipModel.Shown.IconOnly -> {
                 chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
             }
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 f16fcb5..a67b47a 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
@@ -92,11 +92,7 @@
     }
 
     suspend fun getModeIcon(mode: ZenMode): Icon {
-        return mode.getIcon(context, iconLoader).await().asIcon()
-    }
-
-    suspend fun getLockscreenModeIcon(mode: ZenMode): Icon {
-        return mode.getLockscreenIcon(context, iconLoader).await().asIcon()
+        return iconLoader.getIcon(context, mode).await().drawable().asIcon()
     }
 
     /**
@@ -106,8 +102,8 @@
      * standard DND icon for implicit modes, instead of the launcher icon of the associated
      * package).
      */
-    suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
-        return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
+    suspend fun getActiveModeIcon(modes: List<ZenMode>): Icon? {
+        return getMainActiveMode(modes)?.let { m -> getModeIcon(m) }
     }
 
     fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 89227cf..fe1d647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -21,10 +21,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import javax.inject.Inject
@@ -58,7 +58,7 @@
 ) {
 
     private val showingHeadsUpStatusBar: Flow<Boolean> =
-        if (NotificationsHeadsUpRefactor.isEnabled) {
+        if (SceneContainerFlag.isEnabled) {
             headsUpNotificationInteractor.showHeadsUpStatusBar
         } else {
             flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index c7fc445..9c8ef04 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -89,6 +89,9 @@
 import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
 import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
 
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.StateFlow;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -162,6 +165,7 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final JavaAdapter mJavaAdapter;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final StateFlow<Boolean> mIsKeyguardOnAsleepState;
     private final UiModeManager mUiModeManager;
     private ColorScheme mDarkColorScheme;
     private ColorScheme mLightColorScheme;
@@ -202,8 +206,7 @@
             }
             boolean currentUser = userId == mUserTracker.getUserId();
             boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
-                    ? KeyguardState.Companion.deviceIsAsleepInState(
-                            mKeyguardTransitionInteractor.getFinishedState())
+                    ? ThemeOverlayController.this.mIsKeyguardOnAsleepState.getValue()
                     : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
 
             if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -434,6 +437,10 @@
         mUiModeManager = uiModeManager;
         mActivityManager = activityManager;
         dumpManager.registerDumpable(TAG, this);
+
+        Flow<Boolean> isFinishedInAsleepStateFlow = mKeyguardTransitionInteractor
+                .isFinishedInStateWhere(KeyguardState.Companion::deviceIsAsleepInState);
+        mIsKeyguardOnAsleepState = mJavaAdapter.stateInApp(isFinishedInAsleepStateFlow, false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 59c819d..cd32718 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -3,8 +3,6 @@
 import android.annotation.UserIdInt
 import android.content.pm.UserInfo
 import android.os.UserManager
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.refactorGetCurrentUser
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.user.data.repository.UserRepository
 import javax.inject.Inject
@@ -21,23 +19,11 @@
     /** Flow providing the [UserInfo] of the currently selected user. */
     val selectedUserInfo = repository.selectedUserInfo
 
-    /**
-     * Returns the ID of the currently-selected user.
-     *
-     * @param bypassFlag this will ignore the feature flag and get the data from the repository
-     *   instead. This is used for refactored methods that were previously pointing to `userTracker`
-     *   and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled.
-     *   KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together
-     *   with this flag).
-     */
+    /** Returns the ID of the currently-selected user. */
     @UserIdInt
     @JvmOverloads
-    fun getSelectedUserId(bypassFlag: Boolean = false): Int {
-        return if (bypassFlag || refactorGetCurrentUser()) {
-            repository.getSelectedUserInfo().id
-        } else {
-            KeyguardUpdateMonitor.getCurrentUser()
-        }
+    fun getSelectedUserId(): Int {
+        return repository.getSelectedUserInfo().id
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index ae0061b..315a89b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -19,8 +19,7 @@
 import android.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.lifecycle.BaseActivatable
-import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printCollection
 import java.io.PrintWriter
@@ -189,7 +188,7 @@
 ) : SimpleFlowDumper(), ActivatableFlowDumper {
 
     private val registration =
-        object : BaseActivatable() {
+        object : ExclusiveActivatable() {
             override suspend fun onActivated(): Nothing {
                 try {
                     dumpManager.registerCriticalDumpable(
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 055671c..64e056d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -31,7 +31,10 @@
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** A class allowing Java classes to collect on Kotlin flows. */
@@ -58,6 +61,15 @@
     ): Job {
         return scope.launch { flow.collect { consumer.accept(it) } }
     }
+
+    @JvmOverloads
+    fun <T> stateInApp(
+        flow: Flow<T>,
+        initialValue: T,
+        started: SharingStarted = SharingStarted.Eagerly
+    ): StateFlow<T> {
+        return flow.stateIn(scope, started, initialValue)
+    }
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt
rename to packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt
index 6859191..e836731 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt
@@ -17,7 +17,8 @@
 package com.android.systemui.volume
 
 import android.media.IVolumeController
-import com.android.settingslib.media.data.repository.VolumeControllerEvent
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -29,17 +30,17 @@
  * [com.android.settingslib.volume.data.repository.AudioRepository.volumeControllerEvents] and the
  * old code that uses [IVolumeController] interface directly.
  */
-class VolumeControllerCollector
+class VolumeControllerAdapter
 @Inject
-constructor(@Application private val coroutineScope: CoroutineScope) {
+constructor(
+    @Application private val coroutineScope: CoroutineScope,
+    private val audioRepository: AudioRepository,
+) {
 
     /** Collects [Flow] of [VolumeControllerEvent] into [IVolumeController]. */
-    fun collectToController(
-        eventsFlow: Flow<VolumeControllerEvent>,
-        controller: IVolumeController
-    ) =
+    fun collectToController(controller: IVolumeController) {
         coroutineScope.launch {
-            eventsFlow.collect { event ->
+            audioRepository.volumeControllerEvents.collect { event ->
                 when (event) {
                     is VolumeControllerEvent.VolumeChanged ->
                         controller.volumeChanged(event.streamType, event.flags)
@@ -56,4 +57,9 @@
                 }
             }
         }
+    }
+
+    fun notifyVolumeControllerVisible(isVisible: Boolean) {
+        coroutineScope.launch { audioRepository.notifyVolumeControllerVisible(isVisible) }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 1522cc4..28effe9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -68,6 +68,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.volume.MediaSessions;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
@@ -124,7 +125,6 @@
     static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>();
     static {
         STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm);
-        STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
         STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
         STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
         STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
@@ -153,6 +153,7 @@
     private final KeyguardManager mKeyguardManager;
     private final ActivityManager mActivityManager;
     private final UserTracker mUserTracker;
+    private final VolumeControllerAdapter mVolumeControllerAdapter;
     protected C mCallbacks = new C();
     private final State mState = new State();
     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
@@ -197,6 +198,7 @@
             NotificationManager notificationManager,
             VibratorHelper vibrator,
             IAudioService iAudioService,
+            VolumeControllerAdapter volumeControllerAdapter,
             AccessibilityManager accessibilityManager,
             PackageManager packageManager,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -233,6 +235,7 @@
         mVibrator = vibrator;
         mHasVibrator = mVibrator.hasVibrator();
         mAudioService = iAudioService;
+        mVolumeControllerAdapter = volumeControllerAdapter;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
         mUserTracker = userTracker;
@@ -259,10 +262,14 @@
     }
 
     protected void setVolumeController() {
-        try {
-            mAudio.setVolumeController(mVolumeController);
-        } catch (SecurityException e) {
-            Log.w(TAG, "Unable to set the volume controller", e);
+        if (Flags.useVolumeController()) {
+            mVolumeControllerAdapter.collectToController(mVolumeController);
+        } else {
+            try {
+                mAudio.setVolumeController(mVolumeController);
+            } catch (SecurityException e) {
+                Log.w(TAG, "Unable to set the volume controller", e);
+            }
         }
     }
 
@@ -384,7 +391,11 @@
     }
 
     public void notifyVisible(boolean visible) {
-        mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+        if (Flags.useVolumeController()) {
+            mVolumeControllerAdapter.notifyVolumeControllerVisible(visible);
+        } else {
+            mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+        }
     }
 
     public void userActivity() {
@@ -642,7 +653,6 @@
     private static boolean isLogWorthy(int stream) {
         switch (stream) {
             case AudioSystem.STREAM_ALARM:
-            case AudioSystem.STREAM_BLUETOOTH_SCO:
             case AudioSystem.STREAM_MUSIC:
             case AudioSystem.STREAM_RING:
             case AudioSystem.STREAM_SYSTEM:
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index eb91518..7786453 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -704,8 +704,6 @@
                 addRow(AudioManager.STREAM_VOICE_CALL,
                         com.android.internal.R.drawable.ic_phone,
                         com.android.internal.R.drawable.ic_phone, false, false);
-                addRow(AudioManager.STREAM_BLUETOOTH_SCO,
-                        R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false, false);
                 addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system,
                         R.drawable.ic_volume_system_mute, false, false);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 68d12f6..536403c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -20,9 +20,9 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
-import android.os.Handler;
 import android.util.Log;
 
+import com.android.settingslib.volume.data.repository.AudioRepository;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.tiles.DndTile;
@@ -39,23 +39,26 @@
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
 
-    private final Handler mHandler = new Handler();
-
     private boolean mEnabled;
     private final Context mContext;
     private VolumeDialogComponent mVolumeComponent;
     private AudioSharingInteractor mAudioSharingInteractor;
+    private AudioRepository mAudioRepository;
 
     @Inject
-    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent,
+    public VolumeUI(Context context,
+            VolumeDialogComponent volumeDialogComponent,
+            AudioRepository audioRepository,
             AudioSharingInteractor audioSharingInteractor) {
         mContext = context;
         mVolumeComponent = volumeDialogComponent;
+        mAudioRepository = audioRepository;
         mAudioSharingInteractor = audioSharingInteractor;
     }
 
     @Override
     public void start() {
+        mAudioRepository.init();
         boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
         boolean enableSafetyWarning =
                 mContext.getResources().getBoolean(R.bool.enable_safety_warning);
@@ -77,7 +80,8 @@
 
     @Override
     public void dump(PrintWriter pw, String[] args) {
-        pw.print("mEnabled="); pw.println(mEnabled);
+        pw.print("mEnabled=");
+        pw.println(mEnabled);
         if (!mEnabled) return;
         mVolumeComponent.dump(pw, args);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index d39daaf..20d598a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -70,6 +70,7 @@
                 coroutineContext,
                 coroutineScope,
                 volumeLogger,
+                com.android.systemui.Flags.useVolumeController(),
             )
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
index 0451ce6..4be680e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.volume.panel.component.volume.domain.interactor
 
-import android.media.AudioDeviceInfo
 import android.media.AudioManager
-import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -42,7 +40,6 @@
 constructor(
     @VolumePanelScope scope: CoroutineScope,
     mediaOutputInteractor: MediaOutputInteractor,
-    audioRepository: AudioRepository,
     audioModeInteractor: AudioModeInteractor,
 ) {
 
@@ -50,13 +47,12 @@
         combineTransform(
                 mediaOutputInteractor.activeMediaDeviceSessions,
                 mediaOutputInteractor.defaultActiveMediaSession.filterData(),
-                audioRepository.communicationDevice,
                 audioModeInteractor.isOngoingCall,
-            ) { activeSessions, defaultSession, communicationDevice, isOngoingCall ->
+            ) { activeSessions, defaultSession, isOngoingCall ->
                 coroutineScope {
                     val viewModels = buildList {
                         if (isOngoingCall) {
-                            addCall(communicationDevice?.type)
+                            addStream(AudioManager.STREAM_VOICE_CALL)
                         }
 
                         if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
@@ -68,7 +64,7 @@
                         }
 
                         if (!isOngoingCall) {
-                            addCall(communicationDevice?.type)
+                            addStream(AudioManager.STREAM_VOICE_CALL)
                         }
 
                         addStream(AudioManager.STREAM_RING)
@@ -80,14 +76,6 @@
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
-    private fun MutableList<SliderType>.addCall(communicationDeviceType: Int?) {
-        if (communicationDeviceType == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
-            addStream(AudioManager.STREAM_BLUETOOTH_SCO)
-        } else {
-            addStream(AudioManager.STREAM_VOICE_CALL)
-        }
-    }
-
     private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
         if (remoteMediaDeviceSession?.canAdjustVolume == true) {
             add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
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 521f608..ffb1f11 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
@@ -66,7 +66,6 @@
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
-            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
             AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
             AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -75,7 +74,6 @@
         mapOf(
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
-            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
             AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
             AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -91,8 +89,6 @@
                 VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_VOICE_CALL) to
                 VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
-            AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
-                VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_RING) to
                 VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
             AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7aa415b..52fde7e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -354,7 +354,6 @@
         ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                 .when(SubscriptionManager::getDefaultSubscriptionId);
         when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId);
-        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId);
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.systemui.res.R.integer.config_face_auth_supported_posture,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
new file mode 100644
index 0000000..ba990ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test for {@link AccessibilityGestureTargetsObserver}. */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AccessibilityGestureTargetsObserverTest extends SysuiTestCase {
+    private static final int MY_USER_ID = ActivityManager.getCurrentUser();
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private UserTracker mUserTracker;
+    @Mock
+    private AccessibilityGestureTargetsObserver.TargetsChangedListener mListener;
+
+    private AccessibilityGestureTargetsObserver mAccessibilityGestureTargetsObserver;
+
+    private static final String TEST_A11Y_BTN_TARGETS = "Magnification";
+
+    @Before
+    public void setUp() {
+        when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
+        mAccessibilityGestureTargetsObserver = new AccessibilityGestureTargetsObserver(mContext,
+                mUserTracker);
+    }
+
+    @Test
+    public void onChange_haveListener_invokeCallback() {
+        mAccessibilityGestureTargetsObserver.addListener(mListener);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+                MY_USER_ID);
+
+        mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false);
+
+        verify(mListener).onAccessibilityGestureTargetsChanged(TEST_A11Y_BTN_TARGETS);
+    }
+
+    @Test
+    public void onChange_listenerRemoved_noInvokeCallback() {
+        mAccessibilityGestureTargetsObserver.addListener(mListener);
+        mAccessibilityGestureTargetsObserver.removeListener(mListener);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+                MY_USER_ID);
+
+        mAccessibilityGestureTargetsObserver.mContentObserver.onChange(false);
+
+        verify(mListener, never()).onAccessibilityGestureTargetsChanged(anyString());
+    }
+
+    @Test
+    public void getCurrentAccessibilityGestureTargets_expectedValue() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, TEST_A11Y_BTN_TARGETS,
+                MY_USER_ID);
+
+        final String actualValue =
+                mAccessibilityGestureTargetsObserver.getCurrentAccessibilityGestureTargets();
+
+        assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_TARGETS);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 74bc928..681ea75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -59,6 +59,7 @@
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var activeMediaDeviceItem: DeviceItem
     private lateinit var notConnectedDeviceItem: DeviceItem
+    private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
     private lateinit var connectedMediaDeviceItem: DeviceItem
     private lateinit var connectedOtherDeviceItem: DeviceItem
     @Mock private lateinit var dialog: SystemUIDialog
@@ -100,6 +101,15 @@
                 iconWithDescription = null,
                 background = null
             )
+        connectedAudioSharingMediaDeviceItem =
+            DeviceItem(
+                type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+                cachedBluetoothDevice = cachedBluetoothDevice,
+                deviceName = DEVICE_NAME,
+                connectionSummary = DEVICE_CONNECTION_SUMMARY,
+                iconWithDescription = null,
+                background = null
+            )
         connectedOtherDeviceItem =
             DeviceItem(
                 type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
@@ -186,6 +196,21 @@
     }
 
     @Test
+    fun testOnClick_connectedAudioSharingMediaDevice_logClick() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
+                actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+                verify(bluetoothTileDialogLogger)
+                    .logDeviceClick(
+                        cachedBluetoothDevice.address,
+                        DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE
+                    )
+            }
+        }
+    }
+
+    @Test
     fun testOnClick_audioSharingDisabled_shouldNotLaunchSettings() {
         with(kosmos) {
             testScope.runTest {
@@ -415,7 +440,7 @@
     }
 
     @Test
-    fun testOnClick_hasTwoConnectedLeDevice_clickedConnectedLe_shouldLaunchSettings() {
+    fun testOnClick_hasTwoConnectedLeDevice_clickedActiveLe_shouldLaunchSettings() {
         with(kosmos) {
             testScope.runTest {
                 whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
@@ -438,7 +463,7 @@
                     if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
                 }
 
-                actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+                actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
                 verify(activityStarter)
                     .postStartActivityDismissingKeyguard(
                         ArgumentMatchers.any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a27ccc6..ef441c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,18 +17,24 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
 import android.media.AudioManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
+import android.util.Pair
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.flags.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -37,6 +43,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -44,9 +51,11 @@
 class DeviceItemFactoryTest : SysuiTestCase() {
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
+    private lateinit var mockitoSession: StaticMockitoSession
     @Mock private lateinit var cachedDevice: CachedBluetoothDevice
     @Mock private lateinit var bluetoothDevice: BluetoothDevice
-    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var drawable: Drawable
 
     private val availableMediaDeviceItemFactory = AvailableMediaDeviceItemFactory()
     private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
@@ -56,16 +65,21 @@
 
     @Before
     fun setup() {
-        `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
-        `when`(cachedDevice.address).thenReturn(DEVICE_ADDRESS)
-        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
-        `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+        mockitoSession =
+            mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+    }
 
-        context.setMockPackageManager(packageManager)
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
     }
 
     @Test
     fun testAvailableMediaDeviceItemFactory_createFromCachedDevice() {
+        `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+        `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+            .thenReturn(Pair.create(drawable, ""))
         val deviceItem = availableMediaDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
@@ -73,6 +87,10 @@
 
     @Test
     fun testConnectedDeviceItemFactory_createFromCachedDevice() {
+        `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+        `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+            .thenReturn(Pair.create(drawable, ""))
         val deviceItem = connectedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -80,6 +98,10 @@
 
     @Test
     fun testSavedDeviceItemFactory_createFromCachedDevice() {
+        `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+        `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+            .thenReturn(Pair.create(drawable, ""))
         val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -87,6 +109,90 @@
     }
 
     @Test
+    fun testAvailableAudioSharingMediaDeviceItemFactory_createFromCachedDevice() {
+        `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
+            .thenReturn(Pair.create(drawable, ""))
+        val deviceItem =
+            AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+                .create(context, cachedDevice)
+
+        assertThat(deviceItem).isNotNull()
+        assertThat(deviceItem.type)
+            .isEqualTo(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+        assertThat(deviceItem.cachedBluetoothDevice).isEqualTo(cachedDevice)
+        assertThat(deviceItem.deviceName).isEqualTo(DEVICE_NAME)
+        assertThat(deviceItem.isActive).isFalse()
+        assertThat(deviceItem.connectionSummary)
+            .isEqualTo(
+                context.getString(
+                    R.string.quick_settings_bluetooth_device_audio_sharing_or_switch_active
+                )
+            )
+    }
+
+    @Test
+    fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_flagOff_returnsFalse() {
+         // Flags.FLAG_ENABLE_LE_AUDIO_SHARING off or the device doesn't support broadcast
+         // source or assistant.
+        `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
+
+        assertThat(
+                AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+                    .isFilterMatched(context, cachedDevice, audioManager)
+            )
+            .isFalse()
+    }
+
+    @Test
+    fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isActiveDevice_returnsFalse() {
+         // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+         // assistant.
+        `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+        `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(true)
+
+        assertThat(
+                AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+                    .isFilterMatched(context, cachedDevice, audioManager)
+            )
+            .isFalse()
+    }
+
+    @Test
+    fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isNotAvailable_returnsFalse() {
+         // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+         // assistant.
+        `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+        `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
+        `when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
+        `when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
+            .thenReturn(false)
+
+        assertThat(
+                AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+                    .isFilterMatched(context, cachedDevice, audioManager)
+            )
+            .isFalse()
+    }
+
+    @Test
+    fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_returnsTrue() {
+         // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+         // assistant.
+        `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
+        `when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
+        `when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
+        `when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
+            .thenReturn(true)
+
+        assertThat(
+                AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
+                    .isFilterMatched(context, cachedDevice, audioManager)
+            )
+            .isTrue()
+    }
+
+    @Test
     @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
@@ -110,7 +216,6 @@
     @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
-        `when`(cachedDevice.isConnected).thenReturn(false)
 
         assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isFalse()
@@ -119,12 +224,8 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenReturn(ApplicationInfo())
-        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(cachedDevice.isConnected).thenReturn(false)
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
 
         assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isFalse()
@@ -132,7 +233,9 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+    fun testSavedFactory_isFilterMatched_notExclusiveManaged_returnsTrue() {
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
 
@@ -142,45 +245,9 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenReturn(ApplicationInfo().also { it.enabled = false })
-        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(cachedDevice.isConnected).thenReturn(false)
-
-        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isTrue()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenThrow(PackageManager.NameNotFoundException("Test!"))
-        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(cachedDevice.isConnected).thenReturn(false)
-
-        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isTrue()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
-        `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
-        `when`(cachedDevice.isConnected).thenReturn(false)
-
-        assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isFalse()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(true)
 
@@ -191,9 +258,7 @@
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
+        `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
 
         assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isTrue()
@@ -202,21 +267,6 @@
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(false)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
-
-        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isFalse()
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_notBonded_returnsFalse() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
-
         assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isFalse()
     }
@@ -224,13 +274,8 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenReturn(ApplicationInfo())
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(true)
 
         assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isFalse()
@@ -239,9 +284,9 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
+        `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(true)
 
         assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isTrue()
@@ -249,51 +294,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenReturn(ApplicationInfo().also { it.enabled = false })
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
-
-        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isTrue()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
-        `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
-        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
-            .thenThrow(PackageManager.NameNotFoundException("Test!"))
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
-
-        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isTrue()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
-        `when`(bluetoothDevice.isConnected).thenReturn(true)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
-
-        assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
-            .isFalse()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
-        `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
-        `when`(bluetoothDevice.isConnected).thenReturn(false)
-        audioManager.setMode(AudioManager.MODE_NORMAL)
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(BluetoothUtils.isExclusivelyManagedBluetoothDevice(any(), any())).thenReturn(false)
+        `when`(BluetoothUtils.isConnectedBluetoothDevice(any(), any())).thenReturn(false)
 
         assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
             .isFalse()
@@ -310,7 +314,5 @@
     companion object {
         const val DEVICE_NAME = "DeviceName"
         const val CONNECTION_SUMMARY = "ConnectionSummary"
-        private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
-        private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 8e215f9..fd550b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -83,7 +83,9 @@
         PlatformTheme {
             BouncerContent(
                 viewModel =
-                    rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() },
+                    rememberViewModel("test") {
+                        kosmos.bouncerSceneContentViewModelFactory.create()
+                    },
                 layout = BouncerSceneLayout.BESIDE_USER_SWITCHER,
                 modifier = Modifier.fillMaxSize().testTag("BouncerContent"),
                 dialogFactory = bouncerDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 37f1a3d..597ffef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -26,7 +26,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
-import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -250,7 +249,6 @@
         when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded())
                 .thenReturn(mock(Flow.class));
         when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
-        when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
         when(mProcessWrapper.isSystemUser()).thenReturn(true);
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
@@ -275,7 +273,6 @@
                 mKosmos.getNotificationShadeWindowModel(),
                 mKosmos::getCommunalInteractor);
         mFeatureFlags = new FakeFeatureFlags();
-        mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
         mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
 
         DejankUtils.setImmediate(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index a0fe538b..3cbbb64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -62,26 +63,19 @@
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val testScope = kosmos.testScope
 
-    private lateinit var dismissInteractorWithDependencies:
-        KeyguardDismissInteractorFactory.WithDependencies
+    private lateinit var dismissInteractor: KeyguardDismissInteractor
     private lateinit var underTest: KeyguardDismissActionInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        dismissInteractorWithDependencies =
-            KeyguardDismissInteractorFactory.create(
-                context = context,
-                testScope = testScope,
-                keyguardRepository = keyguardRepository,
-            )
-
+        dismissInteractor = kosmos.keyguardDismissInteractor
         underTest =
             KeyguardDismissActionInteractor(
                 repository = keyguardRepository,
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
-                dismissInteractor = dismissInteractorWithDependencies.interactor,
+                dismissInteractor = dismissInteractor,
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = kosmos.sceneInteractor,
                 deviceEntryInteractor = kosmos.deviceEntryInteractor,
@@ -166,9 +160,7 @@
                     willAnimateOnLockscreen = true,
                 )
             )
-            dismissInteractorWithDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(
-                true
-            )
+            kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
             assertThat(executeDismissAction).isEqualTo(onDismissAction)
         }
 
@@ -307,8 +299,7 @@
     @Test
     fun setKeyguardDone() =
         testScope.runTest {
-            val keyguardDoneTiming by
-                collectLastValue(dismissInteractorWithDependencies.interactor.keyguardDone)
+            val keyguardDoneTiming by collectLastValue(dismissInteractor.keyguardDone)
             runCurrent()
 
             underTest.setKeyguardDone(KeyguardDone.LATER)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
index ecb46bd..fabed03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -23,11 +23,18 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.TrustGrantFlags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -38,14 +45,16 @@
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardDismissInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var dispatcher: TestDispatcher
     private lateinit var testScope: TestScope
 
-    private lateinit var underTestDependencies: KeyguardDismissInteractorFactory.WithDependencies
-    private lateinit var underTest: KeyguardDismissInteractor
+    private val underTest = kosmos.keyguardDismissInteractor
     private val userInfo = UserInfo(0, "", 0)
 
     @Before
@@ -54,13 +63,7 @@
         dispatcher = StandardTestDispatcher()
         testScope = TestScope(dispatcher)
 
-        underTestDependencies =
-            KeyguardDismissInteractorFactory.create(
-                context = context,
-                testScope = testScope,
-            )
-        underTest = underTestDependencies.interactor
-        underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
+        kosmos.fakeUserRepository.setUserInfos(listOf(userInfo))
     }
 
     @Test
@@ -69,10 +72,10 @@
             val dismissKeyguardRequestWithoutImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
 
-            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(null)
+            kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null)
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
 
-            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(true)
+            kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
         }
 
@@ -81,7 +84,7 @@
         testScope.runTest {
             val dismissKeyguardRequestWithoutImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
-            underTestDependencies.trustRepository.setRequestDismissKeyguard(
+            kosmos.fakeTrustRepository.setRequestDismissKeyguard(
                 TrustModel(
                     true,
                     0,
@@ -90,8 +93,8 @@
             )
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
 
-            underTestDependencies.powerRepository.setInteractive(true)
-            underTestDependencies.trustRepository.setRequestDismissKeyguard(
+            kosmos.fakePowerRepository.setInteractive(true)
+            kosmos.fakeTrustRepository.setRequestDismissKeyguard(
                 TrustModel(
                     true,
                     0,
@@ -106,15 +109,15 @@
         testScope.runTest {
             val dismissKeyguardRequestWithoutImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
-            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
             runCurrent()
 
             // authenticated different user
-            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
+            kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
 
             // authenticated correct user
-            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
+            kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
         }
 
@@ -123,17 +126,15 @@
         testScope.runTest {
             val dismissKeyguardRequestWithoutImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
-            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
             runCurrent()
 
             // requested from different user
-            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
-                22
-            )
+            kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(22)
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
 
             // requested from correct user
-            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+            kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
                 userInfo.id
             )
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
@@ -159,10 +160,10 @@
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
             val dismissKeyguardRequestWithImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
-            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
             runCurrent()
 
-            underTestDependencies.keyguardRepository.setDismissAction(
+            kosmos.fakeKeyguardRepository.setDismissAction(
                 DismissAction.RunImmediately(
                     onDismissAction = { KeyguardDone.IMMEDIATE },
                     onCancelAction = {},
@@ -170,7 +171,7 @@
                     willAnimateOnLockscreen = true,
                 )
             )
-            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+            kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
                 userInfo.id
             )
             assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
@@ -184,10 +185,10 @@
                 collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
             val dismissKeyguardRequestWithImmediateDismissAction by
                 collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
-            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            kosmos.fakeUserRepository.setSelectedUserInfo(userInfo)
             runCurrent()
 
-            underTestDependencies.keyguardRepository.setDismissAction(
+            kosmos.fakeKeyguardRepository.setDismissAction(
                 DismissAction.RunAfterKeyguardGone(
                     dismissAction = {},
                     onCancelAction = {},
@@ -195,7 +196,7 @@
                     willAnimateOnLockscreen = true,
                 )
             )
-            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+            kosmos.fakeKeyguardBouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
                 userInfo.id
             )
             assertThat(dismissKeyguardRequestWithImmediateDismissAction).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 4e1b12f..43c7ed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -21,7 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -44,7 +44,8 @@
     @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
-    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock
+    private lateinit var keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor
 
     @Before
     fun setUp() {
@@ -57,7 +58,7 @@
                 activityTaskManagerService = activityTaskManagerService,
                 keyguardStateController = keyguardStateController,
                 keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
-                keyguardTransitionInteractor = keyguardTransitionInteractor,
+                keyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 2021400..844a166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -64,20 +64,8 @@
     @Test
     fun onRemovedFromWindow() =
         testScope.runTest {
-            kosmos.primaryBouncerInteractor.setDismissAction(
-                mock(ActivityStarter.OnDismissAction::class.java),
-                {},
-            )
-            assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNotNull()
-
-            val dismissCallback = mock(IKeyguardDismissCallback::class.java)
-            kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
             underTest.onRemovedFromWindow()
-
-            kosmos.fakeExecutor.runAllReady()
             verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
-            verify(dismissCallback).onDismissCancelled()
-            assertThat(kosmos.primaryBouncerInteractor.bouncerDismissAction).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 73b9f57..07f7557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -47,8 +47,11 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -56,7 +59,10 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -144,7 +150,6 @@
     @Mock
     private lateinit var glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel
-    @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
 
     private val kosmos = testKosmos()
 
@@ -163,8 +168,6 @@
     // the viewModel does a `map { 1 - it }` on this value, which is why it's different
     private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f)
 
-    private val intendedFinishedKeyguardStateFlow = MutableStateFlow(KeyguardState.LOCKSCREEN)
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -256,7 +259,6 @@
 
         intendedAlphaMutableStateFlow.value = 1f
         intendedShadeAlphaMutableStateFlow.value = 0f
-        intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
         whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
             .thenReturn(intendedAlphaMutableStateFlow)
         whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
@@ -283,8 +285,6 @@
         whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
-        whenever(transitionInteractor.finishedKeyguardState)
-            .thenReturn(intendedFinishedKeyguardStateFlow)
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
@@ -334,7 +334,7 @@
                     lockscreenToPrimaryBouncerTransitionViewModel,
                 lockscreenToGlanceableHubTransitionViewModel =
                     lockscreenToGlanceableHubTransitionViewModel,
-                transitionInteractor = transitionInteractor,
+                transitionInteractor = kosmos.keyguardTransitionInteractor,
             )
     }
 
@@ -776,7 +776,10 @@
     @Test
     fun shadeExpansionAlpha_changes_whenOnLockscreen() =
         testScope.runTest {
-            intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(from = AOD, to = LOCKSCREEN)
+            )
             intendedShadeAlphaMutableStateFlow.value = 0.25f
             val underTest = collectLastValue(underTest.transitionAlpha)
             assertEquals(0.75f, underTest())
@@ -788,7 +791,10 @@
     @Test
     fun shadeExpansionAlpha_alwaysZero_whenNotOnLockscreen() =
         testScope.runTest {
-            intendedFinishedKeyguardStateFlow.value = KeyguardState.GONE
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(from = AOD, to = GONE)
+            )
             intendedShadeAlphaMutableStateFlow.value = 0.5f
             val underTest = collectLastValue(underTest.transitionAlpha)
             assertEquals(0f, underTest())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
index 67517a2..2ba670c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
@@ -40,7 +40,7 @@
         composeRule.setContent {
             val keepAlive by keepAliveMutable
             if (keepAlive) {
-                rememberActivated {
+                rememberActivated("test") {
                     FakeActivatable(
                         onActivation = { isActive = true },
                         onDeactivation = { isActive = false },
@@ -58,7 +58,7 @@
         composeRule.setContent {
             val keepAlive by keepAliveMutable
             if (keepAlive) {
-                rememberActivated {
+                rememberActivated("name") {
                     FakeActivatable(
                         onActivation = { isActive = true },
                         onDeactivation = { isActive = false },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 7d57220..73f724e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -17,14 +17,8 @@
 package com.android.systemui.lifecycle
 
 import android.view.View
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Text
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertTextEquals
-import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -32,8 +26,6 @@
 import com.android.systemui.util.Assert
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -59,7 +51,7 @@
         composeRule.setContent {
             val keepAlive by keepAliveMutable
             if (keepAlive) {
-                rememberViewModel {
+                rememberViewModel("test") {
                     FakeSysUiViewModel(
                         onActivation = { isActive = true },
                         onDeactivation = { isActive = false },
@@ -77,21 +69,25 @@
         var isActive2 = false
         composeRule.setContent {
             val key by keyMutable
-            rememberViewModel(key) {
-                when (key) {
-                    1 ->
-                        FakeSysUiViewModel(
-                            onActivation = { isActive1 = true },
-                            onDeactivation = { isActive1 = false },
-                        )
-                    2 ->
-                        FakeSysUiViewModel(
-                            onActivation = { isActive2 = true },
-                            onDeactivation = { isActive2 = false },
-                        )
-                    else -> error("unsupported key $key")
+            // Need to explicitly state the type to avoid a weird issue where the factory seems to
+            // return Unit instead of FakeSysUiViewModel. It might be an issue with the compose
+            // compiler.
+            val unused: FakeSysUiViewModel =
+                rememberViewModel("test", key) {
+                    when (key) {
+                        1 ->
+                            FakeSysUiViewModel(
+                                onActivation = { isActive1 = true },
+                                onDeactivation = { isActive1 = false },
+                            )
+                        2 ->
+                            FakeSysUiViewModel(
+                                onActivation = { isActive2 = true },
+                                onDeactivation = { isActive2 = false },
+                            )
+                        else -> error("unsupported key $key")
+                    }
                 }
-            }
         }
         assertThat(isActive1).isTrue()
         assertThat(isActive2).isFalse()
@@ -114,7 +110,7 @@
         composeRule.setContent {
             val keepAlive by keepAliveMutable
             if (keepAlive) {
-                rememberViewModel {
+                rememberViewModel("test") {
                     FakeSysUiViewModel(
                         onActivation = { isActive = true },
                         onDeactivation = { isActive = false },
@@ -138,6 +134,7 @@
         val viewModel = FakeViewModel()
         backgroundScope.launch {
             view.viewModel(
+                traceName = "test",
                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                 factory = { viewModel },
             ) {
@@ -157,51 +154,9 @@
 
         assertThat(viewModel.isActivated).isTrue()
     }
-
-    @Test
-    fun hydratedStateOf() {
-        val keepAliveMutable = mutableStateOf(true)
-        val upstreamStateFlow = MutableStateFlow(true)
-        val upstreamFlow = upstreamStateFlow.map { !it }
-        composeRule.setContent {
-            val keepAlive by keepAliveMutable
-            if (keepAlive) {
-                val viewModel = rememberViewModel {
-                    FakeSysUiViewModel(
-                        upstreamFlow = upstreamFlow,
-                        upstreamStateFlow = upstreamStateFlow,
-                    )
-                }
-
-                Column {
-                    Text(
-                        "upstreamStateFlow=${viewModel.stateBackedByStateFlow}",
-                        Modifier.testTag("upstreamStateFlow")
-                    )
-                    Text(
-                        "upstreamFlow=${viewModel.stateBackedByFlow}",
-                        Modifier.testTag("upstreamFlow")
-                    )
-                }
-            }
-        }
-
-        composeRule.waitForIdle()
-        composeRule
-            .onNode(hasTestTag("upstreamStateFlow"))
-            .assertTextEquals("upstreamStateFlow=true")
-        composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false")
-
-        composeRule.runOnUiThread { upstreamStateFlow.value = false }
-        composeRule.waitForIdle()
-        composeRule
-            .onNode(hasTestTag("upstreamStateFlow"))
-            .assertTextEquals("upstreamStateFlow=false")
-        composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true")
-    }
 }
 
-private class FakeViewModel : SysUiViewModel() {
+private class FakeViewModel : ExclusiveActivatable() {
     var isActivated = false
 
     override suspend fun onActivated(): Nothing {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3b541cd..9eccd9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -38,22 +38,34 @@
 import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Bundle
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
-import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.data.repository.MediaDataRepository
-import com.android.systemui.media.controls.data.repository.MediaFilterRepository
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
 import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
@@ -63,22 +75,21 @@
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -102,6 +113,8 @@
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 private const val KEY = "KEY"
 private const val KEY_2 = "KEY_2"
@@ -125,12 +138,10 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class MediaDataProcessorTest : SysuiTestCase() {
-    val kosmos = testKosmos()
-
+@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
+class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
     @JvmField @Rule val mockito = MockitoJUnit.rule()
-    @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
     @Mock lateinit var transportControls: MediaController.TransportControls
     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -146,10 +157,8 @@
     @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
     @Mock lateinit var mediaDeviceManager: MediaDeviceManager
     @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
-    @Mock lateinit var mediaDataFilter: MediaDataFilterImpl
     @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
-    @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var smartspaceManager: SmartspaceManager
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -157,7 +166,6 @@
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     private lateinit var validRecommendationList: List<SmartspaceAction>
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Mock private lateinit var logger: MediaUiEventLogger
     private lateinit var mediaCarouselInteractor: MediaCarouselInteractor
     private lateinit var mediaDataProcessor: MediaDataProcessor
@@ -170,11 +178,30 @@
     @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
     @Mock private lateinit var ugm: IUriGrantsManager
     @Mock private lateinit var imageSource: ImageDecoder.Source
-    private lateinit var mediaDataRepository: MediaDataRepository
-    private lateinit var testScope: TestScope
-    private lateinit var testDispatcher: TestDispatcher
-    private lateinit var testableLooper: TestableLooper
-    private lateinit var fakeHandler: FakeHandler
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.progressionOf(
+                Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+            )
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+    private val activityStarter = kosmos.activityStarter
+    private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+    private val mediaFilterRepository = kosmos.mediaFilterRepository
+    private val mediaDataFilter = kosmos.mediaDataFilter
 
     private val settings = FakeSettings()
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -185,14 +212,11 @@
             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
             1
         )
-    private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
 
     private lateinit var staticMockSession: MockitoSession
 
     @Before
     fun setup() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         staticMockSession =
             ExtendedMockito.mockitoSession()
                 .mockStatic<UriGrantsManager>(UriGrantsManager::class.java)
@@ -203,17 +227,12 @@
         foregroundExecutor = FakeExecutor(clock)
         backgroundExecutor = FakeExecutor(clock)
         uiExecutor = FakeExecutor(clock)
-        testableLooper = TestableLooper.get(this)
-        fakeHandler = FakeHandler(testableLooper.looper)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
         Settings.Secure.putInt(
             context.contentResolver,
             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
             1
         )
-        testDispatcher = UnconfinedTestDispatcher()
-        testScope = TestScope(testDispatcher)
-        mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
         mediaDataProcessor =
             MediaDataProcessor(
                 context = context,
@@ -222,7 +241,7 @@
                 backgroundExecutor = backgroundExecutor,
                 uiExecutor = uiExecutor,
                 foregroundExecutor = foregroundExecutor,
-                handler = fakeHandler,
+                mainDispatcher = testDispatcher,
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
                 dumpManager = dumpManager,
@@ -232,13 +251,15 @@
                 useQsMediaPlayer = true,
                 systemClock = clock,
                 secureSettings = settings,
-                mediaFlags = mediaFlags,
+                mediaFlags = kosmos.mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                mediaDataRepository = mediaDataRepository,
+                mediaDataRepository = kosmos.mediaDataRepository,
+                mediaDataLoader = { kosmos.mediaDataLoader },
             )
         mediaDataProcessor.start()
+        testScope.runCurrent()
         mediaCarouselInteractor =
             MediaCarouselInteractor(
                 applicationScope = testScope.backgroundScope,
@@ -250,7 +271,7 @@
                 mediaDataCombineLatest = mediaDataCombineLatest,
                 mediaDataFilter = mediaDataFilter,
                 mediaFilterRepository = mediaFilterRepository,
-                mediaFlags = mediaFlags
+                mediaFlags = kosmos.mediaFlags
             )
         mediaCarouselInteractor.start()
         verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
@@ -258,6 +279,7 @@
         session = MediaSession(context, "MediaDataProcessorTestSession")
         mediaNotification =
             SbnBuilder().run {
+                setUser(UserHandle(USER_ID))
                 setPkg(PACKAGE_NAME)
                 modifyNotification(context).also {
                     it.setSmallIcon(android.R.drawable.ic_media_pause)
@@ -285,7 +307,7 @@
                 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
             }
         verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
-        whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
         whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -315,10 +337,11 @@
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
         whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
         whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
         whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
     }
@@ -364,6 +387,7 @@
             PACKAGE_NAME
         )
 
+        testScope.runCurrent()
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
         verify(listener)
@@ -389,7 +413,7 @@
     @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
     }
 
     @Test
@@ -406,8 +430,7 @@
 
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -424,8 +447,7 @@
     fun testOnMetaDataLoaded_withoutExplicitIndicator() {
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -452,10 +474,8 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -499,8 +519,7 @@
             }
 
         mediaDataProcessor.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -586,8 +605,7 @@
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
 
         // Then a media control is created with a placeholder title string
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -617,8 +635,7 @@
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
 
         // Then a media control is created with a placeholder title string
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -659,8 +676,7 @@
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
 
         // Then the media control is added using the notification's title
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -768,8 +784,7 @@
         // GIVEN that the manager has two notifications with resume actions
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
         mediaDataProcessor.onNotificationAdded(KEY_2, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+        testScope.assertRunAllReady(foreground = 2, background = 2)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -856,7 +871,7 @@
     @Test
     fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
         // With the flag enabled to allow remote media to resume
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
 
         // GIVEN that the manager has a notification with a resume action, but is not local
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -887,7 +902,7 @@
     @Test
     fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
         // With the flag enabled to allow remote media to resume
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
 
         // GIVEN that the manager has a remote cast notification
         addNotificationAndLoad(remoteCastNotification)
@@ -1006,7 +1021,7 @@
 
     @Test
     fun testAddResumptionControls_hasPartialProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added with partial progress
         val progress = 0.5
@@ -1033,7 +1048,7 @@
 
     @Test
     fun testAddResumptionControls_hasNotPlayedProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have not been played
         val extras =
@@ -1058,7 +1073,7 @@
 
     @Test
     fun testAddResumptionControls_hasFullProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added with progress info
         val extras =
@@ -1084,7 +1099,7 @@
 
     @Test
     fun testAddResumptionControls_hasNoExtras() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that do not have any extras
         val desc =
@@ -1102,7 +1117,7 @@
 
     @Test
     fun testAddResumptionControls_hasEmptyTitle() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have empty title
         val desc =
@@ -1121,8 +1136,7 @@
         )
 
         // Resumption controls are not added.
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
         verify(listener, never())
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME),
@@ -1136,7 +1150,7 @@
 
     @Test
     fun testAddResumptionControls_hasBlankTitle() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have a blank title
         val desc =
@@ -1155,8 +1169,7 @@
         )
 
         // Resumption controls are not added.
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
         verify(listener, never())
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME),
@@ -1223,8 +1236,7 @@
         mediaDataProcessor.onNotificationAdded(KEY, notif)
 
         // THEN it still loads
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -1341,7 +1353,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
 
@@ -1367,7 +1379,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
         val extras =
             Bundle().apply {
                 putString("package_name", PACKAGE_NAME)
@@ -1401,7 +1413,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
@@ -1433,7 +1445,7 @@
 
     @Test
     fun testSetRecommendationInactive_notifiesListeners() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
@@ -1466,6 +1478,7 @@
     fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
         // WHEN media recommendation setting is off
         settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+        testScope.runCurrent()
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
 
@@ -1483,6 +1496,7 @@
 
         // WHEN the media recommendation setting is turned off
         settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+        testScope.runCurrent()
 
         // THEN listeners are notified
         uiExecutor.advanceClockToLast()
@@ -1503,8 +1517,7 @@
     fun testOnMediaDataTimedOut_updatesLastActiveTime() {
         // GIVEN that the manager has a notification
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // WHEN the notification times out
         clock.advanceTime(100)
@@ -1612,8 +1625,7 @@
 
         // WHEN the notification is loaded
         mediaDataProcessor.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // THEN only the first MAX_COMPACT_ACTIONS are actually set
         verify(listener)
@@ -1648,8 +1660,7 @@
 
         // WHEN the notification is loaded
         mediaDataProcessor.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
         verify(listener)
@@ -1668,7 +1679,7 @@
     @Test
     fun testPlaybackActions_noState_usesNotification() {
         val desc = "Notification Action"
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         whenever(controller.playbackState).thenReturn(null)
 
         val notifWithAction =
@@ -1683,8 +1694,7 @@
             }
         mediaDataProcessor.onNotificationAdded(KEY, notifWithAction)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -1703,7 +1713,7 @@
     @Test
     fun testPlaybackActions_hasPrevNext() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions =
             PlaybackState.ACTION_PLAY or
                 PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1747,7 +1757,7 @@
     @Test
     fun testPlaybackActions_noPrevNext_usesCustom() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1779,7 +1789,7 @@
 
     @Test
     fun testPlaybackActions_connecting() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder =
             PlaybackState.Builder()
@@ -1798,9 +1808,86 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun postWithPlaybackActions_drawablesReused() {
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+        val stateActions =
+            PlaybackState.ACTION_PAUSE or
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+                PlaybackState.ACTION_SKIP_TO_NEXT
+        val stateBuilder =
+            PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+                .setActions(stateActions)
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+        val userEntries by testScope.collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+        mediaDataProcessor.addInternalListener(mediaDataFilter)
+        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+        addNotificationAndLoad()
+
+        assertThat(userEntries).hasSize(1)
+        val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+        addNotificationAndLoad()
+
+        assertThat(userEntries).hasSize(1)
+        val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+        assertThat(secondSemanticActions.playOrPause?.icon)
+            .isEqualTo(firstSemanticActions.playOrPause?.icon)
+        assertThat(secondSemanticActions.playOrPause?.background)
+            .isEqualTo(firstSemanticActions.playOrPause?.background)
+        assertThat(secondSemanticActions.nextOrCustom?.icon)
+            .isEqualTo(firstSemanticActions.nextOrCustom?.icon)
+        assertThat(secondSemanticActions.prevOrCustom?.icon)
+            .isEqualTo(firstSemanticActions.prevOrCustom?.icon)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun postWithPlaybackActions_drawablesNotReused() {
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+        val stateActions =
+            PlaybackState.ACTION_PAUSE or
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+                PlaybackState.ACTION_SKIP_TO_NEXT
+        val stateBuilder =
+            PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 10f)
+                .setActions(stateActions)
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+        val userEntries by testScope.collectLastValue(mediaFilterRepository.selectedUserEntries)
+
+        mediaDataProcessor.addInternalListener(mediaDataFilter)
+        mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+        addNotificationAndLoad()
+
+        assertThat(userEntries).hasSize(1)
+        val firstSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+        addNotificationAndLoad()
+
+        assertThat(userEntries).hasSize(1)
+        val secondSemanticActions = userEntries?.values?.toList()?.get(0)?.semanticActions!!
+
+        assertThat(secondSemanticActions.playOrPause?.icon)
+            .isNotEqualTo(firstSemanticActions.playOrPause?.icon)
+        assertThat(secondSemanticActions.playOrPause?.background)
+            .isNotEqualTo(firstSemanticActions.playOrPause?.background)
+        assertThat(secondSemanticActions.nextOrCustom?.icon)
+            .isNotEqualTo(firstSemanticActions.nextOrCustom?.icon)
+        assertThat(secondSemanticActions.prevOrCustom?.icon)
+            .isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
+    }
+
+    @Test
     fun testPlaybackActions_reservedSpace() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1838,7 +1925,7 @@
 
     @Test
     fun testPlaybackActions_playPause_hasButton() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY_PAUSE
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1875,8 +1962,7 @@
 
         // update to remote cast
         mediaDataProcessor.onNotificationAdded(KEY, remoteCastNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(logger)
             .logPlaybackLocationChange(
                 anyInt(),
@@ -1938,7 +2024,7 @@
 
     @Test
     fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
         whenever(controller.playbackState).thenReturn(state)
 
@@ -1982,6 +2068,7 @@
             pendingIntent,
             PACKAGE_NAME
         )
+        testScope.runCurrent()
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
 
@@ -2060,7 +2147,7 @@
 
     @Test
     fun testRetain_notifPlayer_notifRemoved_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added, times out, and then removed
         addNotificationAndLoad()
@@ -2090,7 +2177,7 @@
 
     @Test
     fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added and times out
         addNotificationAndLoad()
@@ -2108,7 +2195,7 @@
 
     @Test
     fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added and then removed, without timing out
         addNotificationAndLoad()
@@ -2125,7 +2212,7 @@
 
     @Test
     fun testRetain_canResume_removeWhileActive_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control that supports resumption is added
         addNotificationAndLoad()
@@ -2157,8 +2244,8 @@
 
     @Test
     fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2177,8 +2264,8 @@
 
     @Test
     fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2211,8 +2298,8 @@
 
     @Test
     fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2231,8 +2318,8 @@
 
     @Test
     fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2265,7 +2352,7 @@
 
     @Test
     fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2292,7 +2379,7 @@
 
     @Test
     fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2311,7 +2398,7 @@
 
     @Test
     fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2344,8 +2431,8 @@
 
     @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
 
         // When a notiifcation is added and then removed before it is fully processed
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
@@ -2416,6 +2503,23 @@
         assertThat(mediaDataCaptor.value.artwork).isNull()
     }
 
+    private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+        runCurrent()
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            // It doesn't make much sense to count tasks when we use coroutines in loader
+            // so this check is skipped in that scenario.
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+        } else {
+            if (background > 0) {
+                assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+            }
+            if (foreground > 0) {
+                assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+            }
+        }
+    }
+
     /** Helper function to add a basic media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         addNotificationAndLoad(mediaNotification)
@@ -2424,8 +2528,7 @@
     /** Helper function to add the given notification and capture the resulting MediaData */
     private fun addNotificationAndLoad(sbn: StatusBarNotification) {
         mediaDataProcessor.onNotificationAdded(KEY, sbn)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -2459,8 +2562,7 @@
             pendingIntent,
             packageName
         )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         verify(listener)
             .onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 5142730..0c8d880 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -125,6 +126,8 @@
     private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         fakeFgExecutor = FakeExecutor(FakeSystemClock())
@@ -141,6 +144,7 @@
                 { localBluetoothManager },
                 fakeFgExecutor,
                 fakeBgExecutor,
+                kosmos.mediaDeviceLogger,
             )
         manager.addListener(listener)
 
@@ -254,22 +258,17 @@
         // AND that token results in a null route
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        val data = loadMediaAndCaptureDeviceData()
+
         // THEN the device should be disabled
-        val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
     }
 
     @Test
     fun deviceEventOnAddNotification() {
         // WHEN a notification is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
         // THEN the update is dispatched to the listener
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(DEVICE_NAME)
         assertThat(data.icon).isEqualTo(icon)
@@ -417,15 +416,40 @@
         whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         // WHEN a notification is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
         // THEN it uses the route name (instead of device name)
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
     }
 
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableReused() {
+        whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+        val firstData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondData = loadMediaAndCaptureDeviceData()
+
+        assertThat(secondData.icon).isEqualTo(firstData.icon)
+    }
+
+    @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName_drawableNotReused() {
+        whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME)
+        whenever(routingSession.selectedRoutes).thenReturn(listOf("selectedRoute", "selectedRoute"))
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+
+        val firstData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondData = loadMediaAndCaptureDeviceData()
+
+        assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+    }
+
     @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
@@ -433,11 +457,8 @@
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN a notification is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
         // THEN the device is disabled and name is set to null
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isNull()
     }
@@ -449,23 +470,48 @@
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN a notification is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
         // THEN the device is disabled and name and icon are set to "OTHER DEVICE".
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isEqualTo(context.getString(R.string.media_seamless_other_device))
         assertThat(data.icon).isEqualTo(OTHER_DEVICE_ICON_STUB)
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+    @EnableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableReused() {
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+        val firstData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondData = loadMediaAndCaptureDeviceData()
+
+        assertThat(secondData.icon).isEqualTo(firstData.icon)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
+    @DisableFlags(com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_drawableNotReused() {
+        whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        context.orCreateTestableResources.removeOverride(R.drawable.ic_media_home_devices)
+
+        val firstData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondData = loadMediaAndCaptureDeviceData()
+
+        assertThat(secondData.icon).isNotEqualTo(firstData.icon)
+    }
+
     @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
         // GIVEN a notif is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(listener)
         // AND MR2Manager returns null for routing session
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -480,13 +526,12 @@
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isNull()
     }
+
     @RequiresFlagsEnabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS)
     @Test
     fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_returnOtherDevice() {
         // GIVEN a notif is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(listener)
         // AND MR2Manager returns null for routing session
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -507,9 +552,7 @@
     @Test
     fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() {
         // GIVEN a notif is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(listener)
         // GIVEN that MR2Manager returns null for routing session
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -529,9 +572,7 @@
     @Test
     fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_returnsOtherDevice() {
         // GIVEN a notif is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(listener)
         // GIVEN that MR2Manager returns null for routing session
         whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -563,12 +604,8 @@
         whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
         whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
         // Then the device name is the PhoneMediaDevice string
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context))
     }
 
@@ -582,12 +619,8 @@
         whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME)
         whenever(routingSession.isSystemSession).thenReturn(true)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
         // Then the device name is the selected route name
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
     }
 
@@ -597,11 +630,8 @@
         whenever(routingSession.name).thenReturn(null)
         whenever(routingSession.isSystemSession).thenReturn(false)
         // WHEN a notification is added
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
         // THEN the device is enabled and uses the current connected device name
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.name).isEqualTo(DEVICE_NAME)
         assertThat(data.enabled).isTrue()
     }
@@ -611,9 +641,7 @@
         whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
         whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
         // GIVEN a controller with local playback type
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(mr2)
         // WHEN onAudioInfoChanged fires with remote playback type
         whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
@@ -630,9 +658,7 @@
         whenever(playbackInfo.getVolumeControlId()).thenReturn(null)
         whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
         // GIVEN a controller with local playback type
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(mr2)
         // WHEN onAudioInfoChanged fires with a volume control id change
         whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id")
@@ -649,9 +675,7 @@
         whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
         // GIVEN a controller with remote playback type
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
         reset(mr2)
         // WHEN onAudioInfoChanged fires with remote playback type
         val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
@@ -665,9 +689,7 @@
     fun deviceIdChanged_informListener() {
         // GIVEN a notification is added, with a particular device connected
         whenever(device.id).thenReturn(DEVICE_ID)
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
 
         // and later the manager gets a new device ID
         val deviceCallback = captureCallback()
@@ -694,9 +716,7 @@
         // GIVEN a notification is added, with a particular device connected
         whenever(device.id).thenReturn(DEVICE_ID)
         whenever(device.name).thenReturn(DEVICE_NAME)
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
+        loadMediaAndCaptureDeviceData()
 
         // and later the manager gets a new device name
         val deviceCallback = captureCallback()
@@ -725,12 +745,8 @@
         whenever(device.name).thenReturn(DEVICE_NAME)
         val firstIcon = mock(Drawable::class.java)
         whenever(device.icon).thenReturn(firstIcon)
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
 
-        val dataCaptor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
-        verify(listener).onMediaDeviceChanged(eq(KEY), any(), dataCaptor.capture())
+        loadMediaAndCaptureDeviceData()
 
         // and later the manager gets a callback with only the icon changed
         val deviceCallback = captureCallback()
@@ -772,11 +788,7 @@
         setupBroadcastPackage(BROADCAST_APP_NAME)
         broadcastCallback.onBroadcastStarted(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isFalse()
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(DEVICE_NAME)
@@ -791,11 +803,7 @@
         setupBroadcastPackage(BROADCAST_APP_NAME)
         broadcastCallback.onBroadcastStarted(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isTrue()
         assertThat(data.enabled).isTrue()
         assertThat(data.name)
@@ -811,11 +819,7 @@
         setupBroadcastPackage(NORMAL_APP_NAME)
         broadcastCallback.onBroadcastStarted(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isTrue()
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(BROADCAST_APP_NAME)
@@ -829,11 +833,7 @@
         setupLeAudioConfiguration(false)
         broadcastCallback.onBroadcastStopped(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isFalse()
     }
 
@@ -846,11 +846,7 @@
         setupBroadcastPackage(BROADCAST_APP_NAME)
         broadcastCallback.onBroadcastStarted(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isFalse()
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -858,6 +854,82 @@
 
     @Test
     @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+        com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+    )
+    fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesReused() {
+        val broadcastCallback = setupBroadcastCallback()
+        setupLeAudioConfiguration(true)
+        setupBroadcastPackage(BROADCAST_APP_NAME)
+        broadcastCallback.onBroadcastStarted(1, 1)
+
+        val firstDeviceData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+        assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+    }
+
+    @Test
+    @DisableFlags(
+        Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+        com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+    )
+    @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+    fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting_drawablesNotReused() {
+        val broadcastCallback = setupBroadcastCallback()
+        setupLeAudioConfiguration(true)
+        setupBroadcastPackage(BROADCAST_APP_NAME)
+        broadcastCallback.onBroadcastStarted(1, 1)
+
+        val firstDeviceData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+        assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_LEGACY_LE_AUDIO_SHARING,
+        com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+    )
+    @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+    fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableReused() {
+        val broadcastCallback = setupBroadcastCallback()
+        setupLeAudioConfiguration(true)
+        setupBroadcastPackage(NORMAL_APP_NAME)
+        broadcastCallback.onBroadcastStarted(1, 1)
+
+        val firstDeviceData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+        assertThat(firstDeviceData.icon).isEqualTo(secondDeviceData.icon)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+    @DisableFlags(
+        Flags.FLAG_ENABLE_LE_AUDIO_SHARING,
+        com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE
+    )
+    fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting_drawableNotReused() {
+        val broadcastCallback = setupBroadcastCallback()
+        setupLeAudioConfiguration(true)
+        setupBroadcastPackage(NORMAL_APP_NAME)
+        broadcastCallback.onBroadcastStarted(1, 1)
+
+        val firstDeviceData = loadMediaAndCaptureDeviceData()
+        reset(listener)
+        val secondDeviceData = loadMediaAndCaptureDeviceData()
+
+        assertThat(firstDeviceData.icon).isNotEqualTo(secondDeviceData.icon)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
     fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
         val broadcastCallback = setupBroadcastCallback()
@@ -865,11 +937,7 @@
         setupBroadcastPackage(NORMAL_APP_NAME)
         broadcastCallback.onBroadcastStarted(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isFalse()
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
@@ -883,11 +951,7 @@
         setupLeAudioConfiguration(false)
         broadcastCallback.onBroadcastStopped(1, 1)
 
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        fakeBgExecutor.runAllReady()
-        fakeFgExecutor.runAllReady()
-
-        val data = captureDeviceData(KEY)
+        val data = loadMediaAndCaptureDeviceData()
         assertThat(data.showBroadcastButton).isFalse()
         assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description)))
             .isFalse()
@@ -903,13 +967,21 @@
         val callback: BluetoothLeBroadcast.Callback =
             object : BluetoothLeBroadcast.Callback {
                 override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+
                 override fun onBroadcastStartFailed(reason: Int) {}
+
                 override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
+
                 override fun onBroadcastStopFailed(reason: Int) {}
+
                 override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
                 override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
                 override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
                 override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
                 override fun onBroadcastMetadataChanged(
                     broadcastId: Int,
                     metadata: BluetoothLeBroadcastMetadata
@@ -941,4 +1013,12 @@
         verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
         return captor.getValue()
     }
+
+    private fun loadMediaAndCaptureDeviceData(): MediaDeviceData {
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+
+        return captureDeviceData(KEY)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index f8358c5..46c66e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -27,19 +27,24 @@
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -71,14 +76,18 @@
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import javax.inject.Provider
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
@@ -106,6 +115,7 @@
 private const val PAUSED_LOCAL = "paused local"
 private const val PLAYING_LOCAL = "playing local"
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidJUnit4::class)
@@ -183,10 +193,9 @@
                 secureSettings = secureSettings,
                 mediaCarouselViewModel = kosmos.mediaCarouselViewModel,
                 mediaViewControllerFactory = mediaViewControllerFactory,
-                sceneInteractor = kosmos.sceneInteractor,
+                deviceEntryInteractor = kosmos.deviceEntryInteractor,
             )
         verify(configurationController).addCallback(capture(configListener))
-        verify(mediaDataManager).addListener(capture(listener))
         verify(visualStabilityProvider)
             .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
         verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
@@ -405,8 +414,11 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
     }
 
+    @DisableSceneContainer
     @Test
     fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         testPlayerOrdering()
 
         // If smartspace is prioritized
@@ -439,8 +451,11 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
     }
 
+    @DisableSceneContainer
     @Test
     fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         testPlayerOrdering()
         // playing paused player
         listener.value.onMediaDataLoaded(
@@ -547,8 +562,11 @@
         verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
     }
 
+    @DisableSceneContainer
     @Test
     fun testMediaLoaded_ScrollToActivePlayer() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         listener.value.onMediaDataLoaded(
             PLAYING_LOCAL,
             null,
@@ -604,8 +622,11 @@
         )
     }
 
+    @DisableSceneContainer
     @Test
     fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         listener.value.onSmartspaceMediaDataLoaded(
             SMARTSPACE_KEY,
             EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
@@ -647,8 +668,11 @@
         assertEquals(playerIndex, 0)
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         var result = false
         mediaCarouselController.updateHostVisibility = { result = true }
 
@@ -658,8 +682,11 @@
         assertEquals(true, result)
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         var result = false
         mediaCarouselController.updateHostVisibility = { result = true }
 
@@ -788,8 +815,11 @@
         verify(pageIndicator, times(4)).setNumPages(any())
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
 
         // When an update to existing smartspace data is loaded
@@ -804,8 +834,11 @@
         assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
 
         // When inactive smartspace data is loaded
@@ -845,7 +878,6 @@
     }
 
     @DisableSceneContainer
-    @ExperimentalCoroutinesApi
     @Test
     fun testKeyguardGone_showMediaCarousel() =
         kosmos.testScope.runTest {
@@ -869,7 +901,6 @@
         }
 
     @EnableSceneContainer
-    @ExperimentalCoroutinesApi
     @Test
     fun testKeyguardGone_showMediaCarousel_scene_container() =
         kosmos.testScope.runTest {
@@ -887,7 +918,6 @@
             job.cancel()
         }
 
-    @ExperimentalCoroutinesApi
     @Test
     fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
@@ -917,7 +947,6 @@
         }
     }
 
-    @ExperimentalCoroutinesApi
     @Test
     fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
@@ -947,6 +976,74 @@
         }
     }
 
+    @EnableSceneContainer
+    @Test
+    fun deviceEntered_mediaAllowed_notLockedAndHidden() {
+        kosmos.testScope.runTest {
+            val settingsJob =
+                mediaCarouselController.listenForLockscreenSettingChanges(
+                    kosmos.applicationCoroutineScope
+                )
+            secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true)
+            setDeviceEntered(true)
+
+            assertEquals(false, mediaCarouselController.isLockedAndHidden())
+
+            settingsJob.cancel()
+        }
+    }
+
+    @EnableSceneContainer
+    @Test
+    fun deviceEntered_mediaNotAllowed_notLockedAndHidden() {
+        kosmos.testScope.runTest {
+            val settingsJob =
+                mediaCarouselController.listenForLockscreenSettingChanges(
+                    kosmos.applicationCoroutineScope
+                )
+            secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false)
+            setDeviceEntered(true)
+
+            assertEquals(false, mediaCarouselController.isLockedAndHidden())
+
+            settingsJob.cancel()
+        }
+    }
+
+    @EnableSceneContainer
+    @Test
+    fun deviceNotEntered_mediaAllowed_notLockedAndHidden() {
+        kosmos.testScope.runTest {
+            val settingsJob =
+                mediaCarouselController.listenForLockscreenSettingChanges(
+                    kosmos.applicationCoroutineScope
+                )
+            secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true)
+            setDeviceEntered(false)
+
+            assertEquals(false, mediaCarouselController.isLockedAndHidden())
+
+            settingsJob.cancel()
+        }
+    }
+
+    @EnableSceneContainer
+    @Test
+    fun deviceNotEntered_mediaNotAllowed_lockedAndHidden() {
+        kosmos.testScope.runTest {
+            val settingsJob =
+                mediaCarouselController.listenForLockscreenSettingChanges(
+                    kosmos.applicationCoroutineScope
+                )
+            secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false)
+            setDeviceEntered(false)
+
+            assertEquals(true, mediaCarouselController.isLockedAndHidden())
+
+            settingsJob.cancel()
+        }
+    }
+
     @Test
     fun testInvisibleToUserAndExpanded_playersNotListening() {
         // Add players to carousel.
@@ -1023,11 +1120,13 @@
         verify(panel).updateAnimatorDurationScale()
     }
 
+    @DisableSceneContainer
     @Test
     fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         // When resumption is disabled, paused media should be dismissed after being swiped away
         Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
-
         val pausedMedia = DATA.copy(isPlaying = false)
         listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
         mediaCarouselController.onSwipeToDismiss()
@@ -1042,8 +1141,11 @@
         verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
     }
 
+    @DisableSceneContainer
     @Test
     fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         // When resumption is disabled, paused media should be dismissed after being swiped away
         Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
         mediaCarouselController.updateHostVisibility = {}
@@ -1068,6 +1170,7 @@
      * @param function called when a certain configuration change occurs.
      */
     private fun testConfigurationChange(function: () -> Unit) {
+        verify(mediaDataManager).addListener(capture(listener))
         mediaCarouselController.pageIndicator = pageIndicator
         listener.value.onMediaDataLoaded(
             PLAYING_LOCAL,
@@ -1100,4 +1203,30 @@
             mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
         )
     }
+
+    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+        if (isEntered) {
+            // Unlock the device, marking the device as entered
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+        }
+        setScene(
+            if (isEntered) {
+                Scenes.Gone
+            } else {
+                Scenes.Lockscreen
+            }
+        )
+        assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+    }
+
+    private fun TestScope.setScene(key: SceneKey) {
+        kosmos.sceneInteractor.changeScene(key, "test")
+        kosmos.sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        runCurrent()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 521aa5a..1260a65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -70,6 +70,7 @@
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -84,7 +85,6 @@
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.monet.ColorScheme
@@ -141,6 +141,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
 public class MediaControlPanelTest : SysuiTestCase() {
     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
@@ -233,9 +234,7 @@
     @Mock private lateinit var recProgressBar1: SeekBar
     @Mock private lateinit var recProgressBar2: SeekBar
     @Mock private lateinit var recProgressBar3: SeekBar
-    private var shouldShowBroadcastButton: Boolean = false
     @Mock private lateinit var globalSettings: GlobalSettings
-    @Mock private lateinit var mediaFlags: MediaFlags
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -254,7 +253,6 @@
             .thenReturn(applicationInfo)
         whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
         context.setMockPackageManager(packageManager)
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
 
         player =
             object :
@@ -278,7 +276,6 @@
                     lockscreenUserManager,
                     broadcastDialogController,
                     globalSettings,
-                    mediaFlags,
                 ) {
                 override fun loadAnimator(
                     animId: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 6c350cb..2370bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
+import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,7 +41,6 @@
 import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaHostState
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -85,6 +85,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@DisableSceneContainer
 class MediaHierarchyManagerTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -105,7 +106,6 @@
     @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var logger: MediaViewLogger
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -139,7 +139,6 @@
         shadeExpansion = MutableStateFlow(0f)
         whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
         whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion)
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
         mediaHierarchyManager =
             MediaHierarchyManager(
                 context,
@@ -160,7 +159,6 @@
                 testScope.backgroundScope,
                 ResourcesSplitShadeStateController(),
                 logger,
-                mediaFlags,
             )
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index 00b9a46..e765b6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -38,12 +38,12 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.media.controls.ui.view.GutsViewHolder
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.res.R
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
 import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -113,7 +113,6 @@
     @Mock private lateinit var mediaTitleWidgetState: WidgetState
     @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
     @Mock private lateinit var mediaContainerWidgetState: WidgetState
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Mock private lateinit var seekBarViewModel: SeekBarViewModel
     @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
     @Mock private lateinit var globalSettings: GlobalSettings
@@ -140,7 +139,6 @@
                     logger,
                     seekBarViewModel,
                     mainExecutor,
-                    mediaFlags,
                     globalSettings,
                 ) {
                 override fun loadAnimator(
@@ -374,10 +372,9 @@
         verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
     }
 
+    @EnableSceneContainer
     @Test
     fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
         getEnabledChangeListener().onEnabledChanged(enabled = false)
@@ -386,10 +383,9 @@
             .isEqualTo(ConstraintSet.INVISIBLE)
     }
 
+    @EnableSceneContainer
     @Test
     fun attachPlayer_seekBarEnabled_seekBarVisible() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
 
@@ -397,10 +393,9 @@
             .isEqualTo(ConstraintSet.VISIBLE)
     }
 
+    @EnableSceneContainer
     @Test
     fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         getEnabledChangeListener().onEnabledChanged(enabled = true)
 
@@ -413,10 +408,9 @@
             .isEqualTo(ConstraintSet.INVISIBLE)
     }
 
+    @EnableSceneContainer
     @Test
     fun attachPlayer_notScrubbing_scrubbingViewsGone() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.canShowScrubbingTime = true
         getScrubbingChangeListener().onScrubbingChanged(true)
@@ -433,10 +427,9 @@
             .isEqualTo(ConstraintSet.GONE)
     }
 
+    @EnableSceneContainer
     @Test
     fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.canShowScrubbingTime = false
         getScrubbingChangeListener().onScrubbingChanged(true)
@@ -452,10 +445,9 @@
             .isEqualTo(ConstraintSet.GONE)
     }
 
+    @EnableSceneContainer
     @Test
     fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true)
         mediaViewController.setUpPrevButtonInfo(false)
@@ -474,10 +466,9 @@
             .isEqualTo(ConstraintSet.GONE)
     }
 
+    @EnableSceneContainer
     @Test
     fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(false)
         mediaViewController.setUpPrevButtonInfo(true)
@@ -496,10 +487,9 @@
             .isEqualTo(ConstraintSet.GONE)
     }
 
+    @EnableSceneContainer
     @Test
     fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true)
         mediaViewController.setUpPrevButtonInfo(true)
@@ -522,10 +512,9 @@
             .isEqualTo(ConstraintSet.VISIBLE)
     }
 
+    @EnableSceneContainer
     @Test
     fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
-        whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
-
         mediaViewController.attachPlayer(viewHolder)
         mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
         mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index bfbb7ce..a770ee1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -18,7 +18,9 @@
 
 import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -37,6 +39,9 @@
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.provider.Flags;
 import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -47,6 +52,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dump.DumpManager;
@@ -94,6 +100,8 @@
     @Mock
     AccessibilityButtonTargetsObserver mAccessibilityButtonTargetObserver;
     @Mock
+    AccessibilityGestureTargetsObserver mAccessibilityGestureTargetObserver;
+    @Mock
     SystemActions mSystemActions;
     @Mock
     OverviewProxyService mOverviewProxyService;
@@ -152,6 +160,7 @@
                 mAccessibilityManager).addAccessibilityServicesStateChangeListener(any());
         mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
                 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
+                mAccessibilityGestureTargetObserver,
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
@@ -171,6 +180,7 @@
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper);
         verify(mAccessibilityButtonTargetObserver, times(1)).addListener(mNavBarHelper);
+        verify(mAccessibilityGestureTargetObserver, times(1)).addListener(mNavBarHelper);
         verify(mAccessibilityManager, times(1)).addAccessibilityServicesStateChangeListener(
                 mNavBarHelper);
         verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt());
@@ -185,6 +195,7 @@
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         verify(mAccessibilityButtonModeObserver, times(1)).removeListener(mNavBarHelper);
         verify(mAccessibilityButtonTargetObserver, times(1)).removeListener(mNavBarHelper);
+        verify(mAccessibilityGestureTargetObserver, times(1)).removeListener(mNavBarHelper);
         verify(mAccessibilityManager, times(1)).removeAccessibilityServicesStateChangeListener(
                 mNavBarHelper);
         verify(mWm, times(1)).removeRotationWatcher(any());
@@ -353,6 +364,83 @@
         verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
     }
 
+    @Test
+    public void updateA11yState_navBarMode_softwareTargets_isClickable() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+                .thenReturn(createFakeShortcutTargets());
+
+        mNavBarHelper.updateA11yState();
+        long state = mNavBarHelper.getA11yButtonState();
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void updateA11yState_gestureMode_softwareTargets_isClickable() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_GESTURE);
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+                .thenReturn(createFakeShortcutTargets());
+
+        mNavBarHelper.updateA11yState();
+        long state = mNavBarHelper.getA11yButtonState();
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void updateA11yState_gestureNavMode_floatingButtonMode_gestureTargets_isClickable() {
+        mNavBarHelper.onNavigationModeChanged(NAV_BAR_MODE_GESTURAL);
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE))
+                .thenReturn(createFakeShortcutTargets());
+
+        mNavBarHelper.updateA11yState();
+        long state = mNavBarHelper.getA11yButtonState();
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void updateA11yState_navBarMode_gestureTargets_isNotClickable() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.GESTURE))
+                .thenReturn(createFakeShortcutTargets());
+
+        mNavBarHelper.updateA11yState();
+        long state = mNavBarHelper.getA11yButtonState();
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(0);
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
+    public void updateA11yState_singleTarget_clickableButNotLongClickable() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE))
+                .thenReturn(new ArrayList<>(List.of("a")));
+
+        mNavBarHelper.updateA11yState();
+        long state = mNavBarHelper.getA11yButtonState();
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_CLICKABLE).isEqualTo(
+                SYSUI_STATE_A11Y_BUTTON_CLICKABLE);
+        assertThat(state & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE).isEqualTo(0);
+    }
+
     private List<String> createFakeShortcutTargets() {
         return new ArrayList<>(List.of("a", "b", "c", "d"));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 04d140c..2905a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -85,6 +85,7 @@
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.AccessibilityGestureTargetsObserver;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dump.DumpManager;
@@ -287,6 +288,7 @@
             mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class),
                     mock(AccessibilityButtonModeObserver.class),
                     mock(AccessibilityButtonTargetsObserver.class),
+                    mock(AccessibilityGestureTargetsObserver.class),
                     mSystemActions, mOverviewProxyService,
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
                     mKeyguardStateController, mock(NavigationModeController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index ebab049..748c7d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -497,6 +497,17 @@
         assertThat(mTile.mRefreshes).isEqualTo(1);
     }
 
+    @Test
+    public void testStaleTriggeredOnUserSwitch() {
+        mTile.clearRefreshes();
+
+        mTile.userSwitch(10);
+        mTestableLooper.processAllMessages();
+
+        assertFalse(mTile.isListening());
+        assertThat(mTile.mRefreshes).isEqualTo(1);
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b67e111..c8ff52a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,7 +33,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE
 import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler
@@ -58,6 +57,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.controller.keyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
@@ -140,7 +140,8 @@
                     kosmos.sceneDataSourceDelegator,
                     kosmos.notificationStackScrollLayoutController,
                     kosmos.keyguardMediaController,
-                    kosmos.lockscreenSmartspaceController
+                    kosmos.lockscreenSmartspaceController,
+                    logcatLogBuffer("GlanceableHubContainerControllerTest")
                 )
         }
         testableLooper = TestableLooper.get(this)
@@ -186,7 +187,8 @@
                         kosmos.sceneDataSourceDelegator,
                         kosmos.notificationStackScrollLayoutController,
                         kosmos.keyguardMediaController,
-                        kosmos.lockscreenSmartspaceController
+                        kosmos.lockscreenSmartspaceController,
+                        logcatLogBuffer("GlanceableHubContainerControllerTest")
                     )
 
                 // First call succeeds.
@@ -214,7 +216,8 @@
                     kosmos.sceneDataSourceDelegator,
                     kosmos.notificationStackScrollLayoutController,
                     kosmos.keyguardMediaController,
-                    kosmos.lockscreenSmartspaceController
+                    kosmos.lockscreenSmartspaceController,
+                    logcatLogBuffer("GlanceableHubContainerControllerTest")
                 )
 
             assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -237,7 +240,8 @@
                     kosmos.sceneDataSourceDelegator,
                     kosmos.notificationStackScrollLayoutController,
                     kosmos.keyguardMediaController,
-                    kosmos.lockscreenSmartspaceController
+                    kosmos.lockscreenSmartspaceController,
+                    logcatLogBuffer("GlanceableHubContainerControllerTest")
                 )
 
             // Only initView without attaching a view as we don't want the flows to start collecting
@@ -413,13 +417,17 @@
                 // Communal is open.
                 goToScene(CommunalScenes.Communal)
 
-                // Shade shows up.
-                shadeTestUtil.setQsExpansion(0.5f)
-                testableLooper.processAllMessages()
+                // Touch starts and ends.
                 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
                 assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+
+                // Up event is no longer processed
                 assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+
+                // Move event can still be processed
                 assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+                assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+                assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
             }
         }
 
@@ -437,7 +445,7 @@
         }
 
     @Test
-    @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+    @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun gestureExclusionZone_setAfterInit() =
         with(kosmos) {
             testScope.runTest {
@@ -463,7 +471,6 @@
         }
 
     @Test
-    @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
     @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun gestureExclusionZone_setAfterInit_fullSwipe() =
         with(kosmos) {
@@ -484,7 +491,7 @@
         }
 
     @Test
-    @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
+    @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun gestureExclusionZone_setAfterInit_rtl() =
         with(kosmos) {
             testScope.runTest {
@@ -509,7 +516,6 @@
             }
         }
 
-    @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
     @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun gestureExclusionZone_setAfterInit_rtl_fullSwipe() =
         with(kosmos) {
@@ -530,102 +536,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
-    @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
-    fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
-                goToScene(CommunalScenes.Communal)
-
-                assertThat(containerView.systemGestureExclusionRects)
-                    .containsExactly(
-                        Rect(
-                            /* left= */ FAKE_INSETS.left,
-                            /* top= */ TOP_SWIPE_REGION_WIDTH,
-                            /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
-                            /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
-                        ),
-                        Rect(
-                            /* left= */ 0,
-                            /* top= */ 0,
-                            /* right= */ FAKE_INSETS.right,
-                            /* bottom= */ CONTAINER_HEIGHT
-                        )
-                    )
-            }
-        }
-
-    @Test
-    @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
-    fun gestureExclusionZone_setAfterInit_backGestureEnabled_fullSwipe() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
-                goToScene(CommunalScenes.Communal)
-
-                assertThat(containerView.systemGestureExclusionRects)
-                    .containsExactly(
-                        Rect(
-                            /* left= */ 0,
-                            /* top= */ 0,
-                            /* right= */ FAKE_INSETS.right,
-                            /* bottom= */ CONTAINER_HEIGHT
-                        )
-                    )
-            }
-        }
-
-    @Test
-    @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
-    @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
-    fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
-                goToScene(CommunalScenes.Communal)
-
-                assertThat(containerView.systemGestureExclusionRects)
-                    .containsExactly(
-                        Rect(
-                            /* left= */ FAKE_INSETS.left,
-                            /* top= */ TOP_SWIPE_REGION_WIDTH,
-                            /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
-                            /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
-                        ),
-                        Rect(
-                            /* left= */ FAKE_INSETS.left,
-                            /* top= */ 0,
-                            /* right= */ CONTAINER_WIDTH,
-                            /* bottom= */ CONTAINER_HEIGHT
-                        )
-                    )
-            }
-        }
-
-    @Test
-    @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE, FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
-    fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl_fullSwipe() =
-        with(kosmos) {
-            testScope.runTest {
-                whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
-                goToScene(CommunalScenes.Communal)
-
-                assertThat(containerView.systemGestureExclusionRects)
-                    .containsExactly(
-                        Rect(
-                            Rect(
-                                /* left= */ FAKE_INSETS.left,
-                                /* top= */ 0,
-                                /* right= */ CONTAINER_WIDTH,
-                                /* bottom= */ CONTAINER_HEIGHT
-                            )
-                        )
-                    )
-            }
-        }
-
-    @Test
     fun gestureExclusionZone_unsetWhenShadeOpen() =
         with(kosmos) {
             testScope.runTest {
@@ -810,6 +720,30 @@
         }
 
     @Test
+    fun onTouchEvent_shadeExpanding_touchesNotDispatched() =
+        with(kosmos) {
+            testScope.runTest {
+                // On lockscreen.
+                goToScene(CommunalScenes.Blank)
+                whenever(
+                        notificationStackScrollLayoutController.isBelowLastNotification(
+                            any(),
+                            any()
+                        )
+                    )
+                    .thenReturn(true)
+
+                // Shade is open slightly.
+                fakeShadeRepository.setLegacyShadeExpansion(0.01f)
+                testableLooper.processAllMessages()
+
+                // Touches are not consumed.
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+                verify(containerView, never()).onTouchEvent(DOWN_EVENT)
+            }
+        }
+
+    @Test
     fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 523d15c..9481e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -155,7 +155,6 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -163,7 +162,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
-import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -365,13 +363,6 @@
     protected TestScope mTestScope = mKosmos.getTestScope();
     protected ShadeInteractor mShadeInteractor;
     protected PowerInteractor mPowerInteractor;
-    protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
-            new FakeHeadsUpNotificationRepository();
-    protected NotificationsKeyguardViewStateRepository mNotificationsKeyguardViewStateRepository =
-            new NotificationsKeyguardViewStateRepository();
-    protected NotificationsKeyguardInteractor mNotificationsKeyguardInteractor =
-            new NotificationsKeyguardInteractor(mNotificationsKeyguardViewStateRepository);
-    protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
     protected SysuiStatusBarStateController mStatusBarStateController;
@@ -689,12 +680,6 @@
         when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes);
         when(longPressHandlingViewRes.getString(anyInt())).thenReturn("");
 
-
-        mHeadsUpNotificationInteractor =
-                new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
-                        mDeviceEntryFaceAuthInteractor, mKeyguardTransitionInteractor,
-                        mNotificationsKeyguardInteractor, mShadeInteractor);
-
         mNotificationPanelViewController = new NotificationPanelViewController(
                 mView,
                 mMainHandler,
@@ -769,7 +754,6 @@
                 mActivityStarter,
                 mSharedNotificationContainerInteractor,
                 mActiveNotificationsInteractor,
-                mHeadsUpNotificationInteractor,
                 mShadeAnimationInteractor,
                 mKeyguardViewConfigurator,
                 mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 905cc4c..a7fd160 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -58,13 +58,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.DejankUtils;
+import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1375,7 +1375,7 @@
     }
 
     @Test
-    @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @DisableSceneContainer
     public void shadeExpanded_whenHunIsPresent() {
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 64eadb7..90655c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.shade
 
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import android.view.HapticFeedbackConstants
 import android.view.View
@@ -35,7 +34,6 @@
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -243,36 +241,4 @@
         val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
         assertThat(bottomAreaAlpha).isEqualTo(1f)
     }
-
-    @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
-    fun shadeExpanded_whenHunIsPresent() = runTest {
-        launch(mainDispatcher) {
-            givenViewAttached()
-
-            // WHEN a pinned heads up is present
-            mFakeHeadsUpNotificationRepository.setNotifications(
-                FakeHeadsUpRowRepository("key", isPinned = true)
-            )
-        }
-        advanceUntilIdle()
-
-        // THEN the panel should be visible
-        assertThat(mNotificationPanelViewController.isExpanded).isTrue()
-    }
-
-    @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
-    fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
-        launch(mainDispatcher) {
-            givenViewAttached()
-
-            // WHEN a heads up is animating away
-            mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
-        }
-        advanceUntilIdle()
-
-        // THEN the panel should be visible
-        assertThat(mNotificationPanelViewController.isExpanded).isTrue()
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 50131cb..a0d231b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.app.Flags;
 import android.app.Notification;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -41,9 +42,13 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Icon;
+import android.graphics.drawable.ShapeDrawable;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.StatusBarNotification;
 import android.view.ViewGroup;
 
@@ -191,6 +196,34 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+    public void setIcon_withPreloaded_usesPreloaded() {
+        Icon mockIcon = mock(Icon.class);
+        when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+        mStatusBarIcon.icon = mockIcon;
+        mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+        mIconView.set(mStatusBarIcon);
+
+        assertThat(mIconView.getDrawable()).isNotNull();
+        assertThat(mIconView.getDrawable()).isInstanceOf(ShapeDrawable.class);
+    }
+
+    @Test
+    @DisableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+    public void setIcon_withPreloadedButFlagDisabled_ignoresPreloaded() {
+        Icon mockIcon = mock(Icon.class);
+        when(mockIcon.loadDrawableAsUser(any(), anyInt())).thenReturn(new ColorDrawable(1));
+        mStatusBarIcon.icon = mockIcon;
+        mStatusBarIcon.preloadedIcon = new ShapeDrawable();
+
+        mIconView.set(mStatusBarIcon);
+
+        assertThat(mIconView.getDrawable()).isNotNull();
+        assertThat(mIconView.getDrawable()).isInstanceOf(ColorDrawable.class);
+    }
+
+    @Test
     public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
         int dpIconSize = 60;
         int dpDrawingSize = 30;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
new file mode 100644
index 0000000..593f873
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+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.internal.R
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.statusbar.connectivity.IconState
+import com.android.systemui.statusbar.connectivity.NetworkController
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy_Factory
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.policy.SecurityController
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.verifyZeroInteractions
+import kotlin.test.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarSignalPolicyTest : SysuiTestCase() {
+    private val kosmos = Kosmos().also { it.testCase = this }
+
+    private lateinit var underTest: StatusBarSignalPolicy
+
+    private val testScope = TestScope()
+
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+    private val airplaneModeInteractor = kosmos.airplaneModeInteractor
+
+    private val securityController = mock<SecurityController>()
+    private val tunerService = mock<TunerService>()
+    private val statusBarIconController = mock<StatusBarIconController>()
+    private val networkController = mock<NetworkController>()
+    private val carrierConfigTracker = mock<CarrierConfigTracker>()
+
+    private var slotAirplane: String? = null
+
+    @Before
+    fun setup() {
+        underTest =
+            StatusBarSignalPolicy_Factory.newInstance(
+                mContext,
+                statusBarIconController,
+                carrierConfigTracker,
+                networkController,
+                securityController,
+                tunerService,
+                javaAdapter,
+                airplaneModeInteractor,
+            )
+
+        slotAirplane = mContext.getString(R.string.status_bar_airplane)
+    }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagEnabled_iconUpdated() =
+        testScope.runTest {
+            underTest.start()
+            airplaneModeInteractor.setIsAirplaneMode(true)
+            runCurrent()
+            verify(statusBarIconController).setIconVisibility(slotAirplane, true)
+
+            airplaneModeInteractor.setIsAirplaneMode(false)
+            runCurrent()
+            verify(statusBarIconController).setIconVisibility(slotAirplane, false)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagEnabled_iconNotUpdated() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+            clearInvocations(statusBarIconController)
+
+            // Make sure the legacy code path does not change airplane mode when the refactor
+            // flag is enabled.
+            underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+            runCurrent()
+            verifyZeroInteractions(statusBarIconController)
+
+            underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+            runCurrent()
+            verifyZeroInteractions(statusBarIconController)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun statusBarSignalPolicyInitialization_statusBarSignalPolicyRefactorFlagEnabled_initNoOp() =
+        testScope.runTest {
+            // Make sure StatusBarSignalPolicy.init does no initialization when
+            // the refactor flag is disabled.
+            underTest.init()
+            verifyZeroInteractions(securityController, networkController, tunerService)
+        }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagDisabled_iconUpdated() =
+        testScope.runTest {
+            underTest.init()
+
+            underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+            runCurrent()
+            verify(statusBarIconController).setIconVisibility(slotAirplane, true)
+
+            underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
+            runCurrent()
+            verify(statusBarIconController).setIconVisibility(slotAirplane, false)
+        }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagDisabled_iconNotUpdated() =
+        testScope.runTest {
+            underTest.init()
+
+            // Make sure changing airplane mode from airplaneModeRepository does nothing
+            // if the StatusBarSignalPolicyRefactor is not enabled.
+            airplaneModeInteractor.setIsAirplaneMode(true)
+            runCurrent()
+            verifyZeroInteractions(statusBarIconController)
+
+            airplaneModeInteractor.setIsAirplaneMode(false)
+            runCurrent()
+            verifyZeroInteractions(statusBarIconController)
+        }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
+    fun statusBarSignalPolicyInitialization_statusBarSignalPolicyRefactorFlagDisabled_startNoOp() =
+        testScope.runTest {
+            // Make sure StatusBarSignalPolicy.start does no initialization when
+            // the refactor flag is disabled.
+            underTest.start()
+            verifyZeroInteractions(securityController, networkController, tunerService)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index ce79fbd..7bc6d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -132,10 +132,10 @@
             )
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+                .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
             assertThat(icon.contentDescription).isNotNull()
@@ -170,10 +170,10 @@
             )
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+                .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
             assertThat(icon.contentDescription).isNotNull()
@@ -206,10 +206,10 @@
             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+                .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
             assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index a8d2c5b..77992db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -127,7 +127,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
             assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -146,7 +146,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
             assertThat((icon.contentDescription as ContentDescription.Resource).res)
@@ -184,7 +184,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
             // This content description is just generic "Casting", not "Casting screen"
@@ -214,7 +214,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
             // MediaProjection == screen casting, so this content description reflects that we're
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
new file mode 100644
index 0000000..8576893
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+class DemoRonChipViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val commandRegistry = kosmos.commandRegistry
+    private val pw = PrintWriter(StringWriter())
+
+    private val underTest = kosmos.demoRonChipViewModel
+
+    @Before
+    fun setUp() {
+        underTest.start()
+        whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+            .thenReturn(BitmapDrawable())
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun chip_flagOff_hidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addDemoRonChip()
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun chip_noPackage_hidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron"))
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun chip_hasPackage_shown() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun chip_hasText_shownWithText() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            commandRegistry.onShellCommand(
+                pw,
+                arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test")
+            )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun chip_hasHideArg_hidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            // First, show a chip
+            addDemoRonChip()
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // Then, hide the chip
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide"))
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    private fun addDemoRonChip() {
+        Companion.addDemoRonChip(commandRegistry, pw)
+    }
+
+    companion object {
+        fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 804eb5c..16101bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -150,7 +150,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
             assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index a2ef599..791a21d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -135,7 +135,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
             assertThat(icon.contentDescription).isNotNull()
@@ -152,7 +152,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
             assertThat(icon.contentDescription).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index a724cfaa..4977c548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -154,5 +154,7 @@
         }
 
     private fun createIcon(@DrawableRes drawable: Int) =
-        OngoingActivityChipModel.ChipIcon.Basic(Icon.Resource(drawable, contentDescription = null))
+        OngoingActivityChipModel.ChipIcon.SingleColorIcon(
+            Icon.Resource(drawable, contentDescription = null)
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 556ec6a..bd5df07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -19,9 +19,12 @@
 import android.content.DialogInterface
 import android.content.packageManager
 import android.content.pm.PackageManager
+import android.graphics.drawable.BitmapDrawable
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -36,8 +39,11 @@
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.commandline.commandRegistry
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -45,6 +51,8 @@
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -68,11 +76,14 @@
     private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
     private val systemClock = kosmos.fakeSystemClock
+    private val commandRegistry = kosmos.commandRegistry
 
     private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
     private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
     private val callRepo = kosmos.ongoingCallRepository
 
+    private val pw = PrintWriter(StringWriter())
+
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val chipBackgroundView = mock<ChipBackgroundContainer>()
     private val chipView =
@@ -90,6 +101,9 @@
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
+        kosmos.demoRonChipViewModel.start()
+        whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+            .thenReturn(BitmapDrawable())
     }
 
     @Test
@@ -169,15 +183,24 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
         testScope.runTest {
-            // Start with just the lower priority call chip
-            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+            // Start with just the lowest priority chip shown
+            addDemoRonChip(commandRegistry, pw)
+            // And everything else hidden
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             screenRecordState.value = ScreenRecordModel.DoingNothing
 
             val latest by collectLastValue(underTest.chip)
 
+            assertIsDemoRonChip(latest)
+
+            // WHEN the higher priority call chip is added
+            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+
+            // THEN the higher priority call chip is used
             assertIsCallChip(latest)
 
             // WHEN the higher priority media projection chip is added
@@ -199,14 +222,15 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
         testScope.runTest {
             // WHEN all chips are active
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+            addDemoRonChip(commandRegistry, pw)
 
             val latest by collectLastValue(underTest.chip)
 
@@ -224,6 +248,12 @@
 
             // THEN the lower priority call is used
             assertIsCallChip(latest)
+
+            // WHEN the higher priority call is removed
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+            // THEN the lower priority demo RON is used
+            assertIsDemoRonChip(latest)
         }
 
     /** Regression test for b/347726238. */
@@ -338,7 +368,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
         }
@@ -347,7 +377,7 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
         }
@@ -356,9 +386,15 @@
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
             val icon =
                 (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.Basic)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
                     .impl as Icon.Resource
             assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
         }
+
+        fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) {
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown).icon)
+                .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java)
+        }
     }
 }
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 1717f4c..8d1228c 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
@@ -95,7 +95,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -1206,7 +1205,7 @@
 
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1222,7 +1221,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
         // GIVEN NSSL would be ready for HUN animations, BUT it is expanded
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1241,7 +1240,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1259,7 +1258,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1295,7 +1294,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index dfee2ed..76dc65c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -17,17 +17,24 @@
 package com.android.systemui.statusbar.phone
 
 import android.app.AlarmManager
+import android.app.AutomaticZenRule
+import android.app.NotificationManager
 import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResourcesManager
 import android.content.SharedPreferences
+import android.net.Uri
 import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.service.notification.SystemZenRules
+import android.service.notification.ZenModeConfig
 import android.telecom.TelecomManager
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -53,12 +60,13 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.DateFormatUtil
 import com.android.systemui.util.time.FakeSystemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -83,7 +91,10 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
 
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
@@ -91,7 +102,11 @@
 @SmallTest
 class PhoneStatusBarPolicyTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val zenModeRepository = kosmos.fakeZenModeRepository
+
     companion object {
+        private const val ZEN_SLOT = "zen"
         private const val ALARM_SLOT = "alarm"
         private const val CAST_SLOT = "cast"
         private const val SCREEN_RECORD_SLOT = "screen_record"
@@ -109,7 +124,6 @@
     @Mock private lateinit var userInfoController: UserInfoController
     @Mock private lateinit var rotationLockController: RotationLockController
     @Mock private lateinit var dataSaverController: DataSaverController
-    @Mock private lateinit var zenModeController: ZenModeController
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var locationController: LocationController
@@ -133,6 +147,7 @@
 
     private val testScope = TestScope(UnconfinedTestDispatcher())
     private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+    private val zenModeController = FakeZenModeController()
 
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -374,6 +389,102 @@
         verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
     }
 
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+    fun zenModeInteractorActiveModeChanged_showsModeIcon() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            reset(iconController)
+
+            zenModeRepository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setId("bedtime")
+                        .setName("Bedtime Mode")
+                        .setType(AutomaticZenRule.TYPE_BEDTIME)
+                        .setActive(true)
+                        .setPackage("some.package")
+                        .setIconResId(123)
+                        .build(),
+                    TestModeBuilder()
+                        .setId("other")
+                        .setName("Other Mode")
+                        .setType(AutomaticZenRule.TYPE_OTHER)
+                        .setActive(true)
+                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                        .setIconResId(456)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+            verify(iconController)
+                .setResourceIcon(
+                    eq(ZEN_SLOT),
+                    eq("some.package"),
+                    eq(123),
+                    eq(null),
+                    eq("Bedtime Mode")
+                )
+
+            zenModeRepository.deactivateMode("bedtime")
+            runCurrent()
+
+            verify(iconController)
+                .setResourceIcon(eq(ZEN_SLOT), eq(null), eq(456), eq(null), eq("Other Mode"))
+
+            zenModeRepository.deactivateMode("other")
+            runCurrent()
+
+            verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+        }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+    fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() {
+        statusBarPolicy.init()
+        reset(iconController)
+
+        zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+        verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+        verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+        verify(iconController, never()).setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+    }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+    fun zenModeInteractorActiveModeChanged_withFlagDisabled_ignored() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            reset(iconController)
+
+            zenModeRepository.addMode(id = "Bedtime", active = true)
+            runCurrent()
+
+            verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
+            verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
+            verify(iconController, never())
+                .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any())
+        }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
+    fun zenModeControllerOnGlobalZenChanged_withFlagDisabled_updatesDndIcon() {
+        statusBarPolicy.init()
+        reset(iconController)
+
+        zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
+
+        verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+        verify(iconController).setIcon(eq(ZEN_SLOT), anyInt(), eq("Priority only"))
+
+        zenModeController.setZen(Settings.Global.ZEN_MODE_OFF, null, null)
+
+        verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+    }
+
     private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
         return AlarmManager.AlarmClockInfo(10L, null)
     }
@@ -412,6 +523,7 @@
             privacyItemController,
             privacyLogger,
             fakeConnectedDisplayStateProvider,
+            kosmos.zenModeInteractor,
             JavaAdapter(testScope.backgroundScope)
         )
     }
@@ -433,4 +545,51 @@
         override val concurrentDisplaysInProgress: Flow<Boolean>
             get() = TODO("Not yet implemented")
     }
+
+    private class FakeZenModeController : ZenModeController {
+
+        private val callbacks = mutableListOf<ZenModeController.Callback>()
+        private var zen = Settings.Global.ZEN_MODE_OFF
+        private var consolidatedPolicy = NotificationManager.Policy(0, 0, 0)
+
+        override fun addCallback(listener: ZenModeController.Callback) {
+            callbacks.add(listener)
+        }
+
+        override fun removeCallback(listener: ZenModeController.Callback) {
+            callbacks.remove(listener)
+        }
+
+        override fun setZen(zen: Int, conditionId: Uri?, reason: String?) {
+            this.zen = zen
+            callbacks.forEach { it.onZenChanged(zen) }
+        }
+
+        override fun getZen(): Int = zen
+
+        override fun getManualRule(): ZenModeConfig.ZenRule = throw NotImplementedError()
+
+        override fun getConfig(): ZenModeConfig = throw NotImplementedError()
+
+        fun setConsolidatedPolicy(policy: NotificationManager.Policy) {
+            this.consolidatedPolicy = policy
+            callbacks.forEach { it.onConsolidatedPolicyChanged(consolidatedPolicy) }
+        }
+
+        override fun getConsolidatedPolicy(): NotificationManager.Policy = consolidatedPolicy
+
+        override fun getNextAlarm() = throw NotImplementedError()
+
+        override fun isZenAvailable() = throw NotImplementedError()
+
+        override fun getEffectsSuppressor() = throw NotImplementedError()
+
+        override fun isCountdownConditionSupported() = throw NotImplementedError()
+
+        override fun getCurrentUser() = throw NotImplementedError()
+
+        override fun isVolumeRestricted() = throw NotImplementedError()
+
+        override fun areNotificationsHiddenInShade() = throw NotImplementedError()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9b61105..3e3c046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -83,10 +83,10 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
+import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -108,7 +108,9 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import com.google.common.truth.Truth;
 
@@ -169,12 +171,14 @@
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private DeviceEntryInteractor mDeviceEntryInteractor;
     @Mock private SceneInteractor mSceneInteractor;
+    @Mock private DismissCallbackRegistry mDismissCallbackRegistry;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
             mBouncerExpansionCallback;
     private FakeKeyguardStateController mKeyguardStateController =
             spy(new FakeKeyguardStateController());
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Mock
     private ViewRootImpl mViewRootImpl;
@@ -230,15 +234,16 @@
                         mUdfpsOverlayInteractor,
                         mActivityStarter,
                         mKeyguardTransitionInteractor,
+                        mock(KeyguardDismissTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
-                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
-                        () -> mock(KeyguardSurfaceBehindInteractor.class),
                         mock(JavaAdapter.class),
                         () -> mSceneInteractor,
                         mock(StatusBarKeyguardViewManagerInteractor.class),
-                        () -> mDeviceEntryInteractor) {
+                        mExecutor,
+                        () -> mDeviceEntryInteractor,
+                        mDismissCallbackRegistry) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -752,15 +757,16 @@
                         mUdfpsOverlayInteractor,
                         mActivityStarter,
                         mock(KeyguardTransitionInteractor.class),
+                        mock(KeyguardDismissTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
-                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
-                        () -> mock(KeyguardSurfaceBehindInteractor.class),
                         mock(JavaAdapter.class),
                         () -> mSceneInteractor,
                         mock(StatusBarKeyguardViewManagerInteractor.class),
-                        () -> mDeviceEntryInteractor) {
+                        mExecutor,
+                        () -> mDeviceEntryInteractor,
+                        mDismissCallbackRegistry) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -772,7 +778,11 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testResetHideBouncerWhenShowing_alternateBouncerHides() {
+        reset(mDismissCallbackRegistry);
+        reset(mPrimaryBouncerInteractor);
+
         // GIVEN the keyguard is showing
         reset(mAlternateBouncerInteractor);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
@@ -780,8 +790,10 @@
         // WHEN SBKV is reset with hideBouncerWhenShowing=true
         mStatusBarKeyguardViewManager.reset(true);
 
-        // THEN alternate bouncer is hidden
+        // THEN alternate bouncer is hidden and dismiss actions reset
         verify(mAlternateBouncerInteractor).hide();
+        verify(mDismissCallbackRegistry).notifyDismissCancelled();
+        verify(mPrimaryBouncerInteractor).setDismissAction(eq(null), eq(null));
     }
 
     @Test
@@ -1084,6 +1096,9 @@
                 .thenReturn(KeyguardState.LOCKSCREEN);
 
         reset(mCentralSurfaces);
+        // Advance past reattempts
+        mStatusBarKeyguardViewManager.setAttemptsToShowBouncer(10);
+
         mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
         verify(mPrimaryBouncerInteractor).show(true);
         verify(mCentralSurfaces).showKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
index 230ddf9..48c2cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
@@ -56,11 +56,11 @@
             runCurrent()
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
 
-            sceneRepository.isRemoteUserInteractionOngoing.value = true
+            sceneRepository.isRemoteUserInputOngoing.value = true
             runCurrent()
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
 
-            sceneRepository.isRemoteUserInteractionOngoing.value = false
+            sceneRepository.isRemoteUserInputOngoing.value = false
             runCurrent()
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
         }
@@ -71,7 +71,7 @@
         testScope.runTest {
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
 
-            sceneRepository.isRemoteUserInteractionOngoing.value = true
+            sceneRepository.isRemoteUserInputOngoing.value = true
             runCurrent()
 
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
index 19abbd5..26a57e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.phone.ui
 
+import android.graphics.drawable.ColorDrawable
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.StatusBarIcon
@@ -406,6 +408,33 @@
             .isInstanceOf(StatusBarIconHolder.BindableIconHolder::class.java)
     }
 
+    @Test
+    fun setIcon_setsIconInHolder() {
+        underTest.setIcon("slot", 123, "description")
+
+        val iconHolder = iconList.getIconHolder("slot", 0)
+        assertThat(iconHolder).isNotNull()
+        assertThat(iconHolder?.icon?.pkg).isEqualTo(mContext.packageName)
+        assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+        assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo(mContext.packageName)
+        assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
+    fun setResourceIcon_setsIconAndPreloadedIconInHolder() {
+        val drawable = ColorDrawable(1)
+        underTest.setResourceIcon("slot", "some.package", 123, drawable, "description")
+
+        val iconHolder = iconList.getIconHolder("slot", 0)
+        assertThat(iconHolder).isNotNull()
+        assertThat(iconHolder?.icon?.pkg).isEqualTo("some.package")
+        assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123)
+        assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo("some.package")
+        assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description")
+        assertThat(iconHolder?.icon?.preloadedIcon).isEqualTo(drawable)
+    }
+
     private fun createExternalIcon(): StatusBarIcon {
         return StatusBarIcon(
             "external.package",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index f486787..0945742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -21,60 +21,61 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.VpnTransportInfo
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
+import com.android.systemui.testKosmos
 import com.android.systemui.tuner.TunerService
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ConnectivityRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var underTest: ConnectivityRepositoryImpl
 
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var connectivitySlots: ConnectivitySlots
-    @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var logger: ConnectivityInputLogger
-    private lateinit var testScope: TestScope
-    @Mock private lateinit var tunerService: TunerService
+    private val connectivityManager = mock<ConnectivityManager>()
+    private val connectivitySlots = mock<ConnectivitySlots>()
+    private val dumpManager = kosmos.dumpManager
+    private val logger = ConnectivityInputLogger(FakeLogBuffer.Factory.create())
+    private val testScope = kosmos.testScope
+    private val tunerService = mock<TunerService>()
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testScope = TestScope(UnconfinedTestDispatcher())
         createAndSetRepo()
     }
 
@@ -89,12 +90,10 @@
             // config_statusBarIconsToExclude when it's first constructed
             createAndSetRepo()
 
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
-
-            job.cancel()
         }
 
     @Test
@@ -102,14 +101,12 @@
         testScope.runTest {
             setUpEthernetWifiMobileSlotNames()
 
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
 
             assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
-
-            job.cancel()
         }
 
     @Test
@@ -117,19 +114,16 @@
         testScope.runTest {
             setUpEthernetWifiMobileSlotNames()
 
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
 
             // WHEN onTuningChanged with the wrong key
             getTunable().onTuningChanged("wrongKey", SLOT_WIFI)
-            yield()
 
             // THEN we didn't update our value and still have the old one
             assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
-
-            job.cancel()
         }
 
     @Test
@@ -143,8 +137,8 @@
             // config_statusBarIconsToExclude when it's first constructed
             createAndSetRepo()
 
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             // First, update the slots
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
@@ -152,19 +146,16 @@
 
             // WHEN we update to a null value
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, null)
-            yield()
 
             // THEN we go back to our default value
             assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
-
-            job.cancel()
         }
 
     @Test
     fun forceHiddenSlots_someInvalidSlotNames_flowHasValidSlotsOnly() =
         testScope.runTest {
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             whenever(connectivitySlots.getSlotFromName(SLOT_WIFI)).thenReturn(ConnectivitySlot.WIFI)
             whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE)).thenReturn(null)
@@ -172,8 +163,6 @@
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_MOBILE")
 
             assertThat(latest).containsExactly(ConnectivitySlot.WIFI)
-
-            job.cancel()
         }
 
     @Test
@@ -181,23 +170,21 @@
         testScope.runTest {
             setUpEthernetWifiMobileSlotNames()
 
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             // WHEN there's empty and blank slot names
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_MOBILE,  ,,$SLOT_WIFI")
 
             // THEN we skip that slot but still process the other ones
             assertThat(latest).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.MOBILE)
-
-            job.cancel()
         }
 
     @Test
     fun forceHiddenSlots_allInvalidOrEmptySlotNames_flowHasEmpty() =
         testScope.runTest {
-            var latest: Set<ConnectivitySlot>? = null
-            val job = underTest.forceHiddenSlots.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             whenever(connectivitySlots.getSlotFromName(SLOT_WIFI)).thenReturn(null)
             whenever(connectivitySlots.getSlotFromName(SLOT_ETHERNET)).thenReturn(null)
@@ -210,8 +197,6 @@
                 )
 
             assertThat(latest).isEmpty()
-
-            job.cancel()
         }
 
     @Test
@@ -219,29 +204,25 @@
         testScope.runTest {
             setUpEthernetWifiMobileSlotNames()
 
-            var latest1: Set<ConnectivitySlot>? = null
-            val job1 = underTest.forceHiddenSlots.onEach { latest1 = it }.launchIn(this)
+            val latest1 by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_ETHERNET")
 
             assertThat(latest1).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
 
             // WHEN we add a second subscriber after having already emitted a value
-            var latest2: Set<ConnectivitySlot>? = null
-            val job2 = underTest.forceHiddenSlots.onEach { latest2 = it }.launchIn(this)
+            val latest2 by collectLastValue(underTest.forceHiddenSlots)
+            runCurrent()
 
             // THEN the second subscribe receives the already-emitted value
             assertThat(latest2).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
-
-            job1.cancel()
-            job2.cancel()
         }
 
     @Test
     fun defaultConnections_noTransports_nothingIsDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -256,15 +237,12 @@
             assertThat(latest!!.wifi.isDefault).isFalse()
             assertThat(latest!!.ethernet.isDefault).isFalse()
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_cellularTransport_mobileIsDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -279,15 +257,12 @@
             assertThat(latest!!.wifi.isDefault).isFalse()
             assertThat(latest!!.ethernet.isDefault).isFalse()
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_wifiTransport_wifiIsDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -302,15 +277,12 @@
             assertThat(latest!!.ethernet.isDefault).isFalse()
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
             assertThat(latest!!.mobile.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_ethernetTransport_ethernetIsDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -325,15 +297,12 @@
             assertThat(latest!!.wifi.isDefault).isFalse()
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
             assertThat(latest!!.mobile.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_carrierMergedViaWifi_wifiAndCarrierMergedDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -350,15 +319,12 @@
             assertThat(latest!!.wifi.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.mobile.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_carrierMergedViaMobile_mobileCarrierMergedWifiDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -375,15 +341,12 @@
             assertThat(latest!!.mobile.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.wifi.isDefault).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -400,15 +363,13 @@
             assertThat(latest!!.wifi.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.mobile.isDefault).isFalse()
-
-            job.cancel()
         }
 
+    /** VCN over W+ (aka VCN over carrier merged). See b/352162710#comment27 scenario #1. */
     @Test
     fun defaultConnections_carrierMergedViaMobileWithVcnTransport_mobileCarrierMergedWifiDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -425,15 +386,48 @@
             assertThat(latest!!.mobile.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.wifi.isDefault).isTrue()
+        }
 
-            job.cancel()
+    /** VPN over W+ (aka VPN over carrier merged). See b/352162710#comment27 scenario #2. */
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
+    fun defaultConnections_vpnOverCarrierMerged_carrierMergedDefault() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultConnections)
+
+            // Underlying carrier merged network
+            val underlyingCarrierMergedNetwork = mock<Network>()
+            val carrierMergedInfo =
+                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+            val underlyingCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+                }
+            whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+                .thenReturn(underlyingCapabilities)
+
+            val mainCapabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
+                    // Transports are WIFI|VPN, *not* CELLULAR.
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(VpnTransportInfo(0, null, false, false))
+                    whenever(it.underlyingNetworks)
+                        .thenReturn(listOf(underlyingCarrierMergedNetwork))
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+            assertThat(latest!!.carrierMerged.isDefault).isTrue()
         }
 
     @Test
     fun defaultConnections_notCarrierMergedViaWifi_carrierMergedNotDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
@@ -448,15 +442,12 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_notCarrierMergedViaMobile_carrierMergedNotDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
@@ -471,15 +462,12 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest!!.carrierMerged.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_transportInfoNotWifi_wifiNotDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -492,8 +480,6 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest!!.wifi.isDefault).isFalse()
-
-            job.cancel()
         }
 
     @Test
@@ -531,8 +517,7 @@
     @Test
     fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             // Underlying carrier merged network
             val underlyingCarrierMergedNetwork = mock<Network>()
@@ -560,16 +545,17 @@
             assertThat(latest!!.mobile.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.wifi.isDefault).isTrue()
-
-            job.cancel()
         }
 
-    /** Test for b/225902574. */
+    /**
+     * Test for b/225902574: VPN over VCN over W+ (aka VPN over VCN over carrier merged).
+     *
+     * Also see b/352162710#comment27 scenario #3 and b/352162710#comment30.
+     */
     @Test
     fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             // Underlying carrier merged network
             val underlyingCarrierMergedNetwork = mock<Network>()
@@ -587,6 +573,7 @@
             val mainCapabilities =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(null)
                     whenever(it.underlyingNetworks)
                         .thenReturn(listOf(underlyingCarrierMergedNetwork))
@@ -597,15 +584,12 @@
             assertThat(latest!!.mobile.isDefault).isTrue()
             assertThat(latest!!.carrierMerged.isDefault).isTrue()
             assertThat(latest!!.wifi.isDefault).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_multipleTransports_multipleDefault() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -619,15 +603,12 @@
             assertThat(latest!!.mobile.isDefault).isTrue()
             assertThat(latest!!.ethernet.isDefault).isTrue()
             assertThat(latest!!.wifi.isDefault).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_hasValidated_isValidatedTrue() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -638,14 +619,12 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest!!.isValidated).isTrue()
-            job.cancel()
         }
 
     @Test
     fun defaultConnections_noValidated_isValidatedFalse() =
         testScope.runTest {
-            var latest: DefaultConnectionModel? = null
-            val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnections)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -656,7 +635,6 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest!!.isValidated).isFalse()
-            job.cancel()
         }
 
     @Test
@@ -669,8 +647,7 @@
         testScope.runTest {
             val vcnInfo = VcnTransportInfo(SUB_1_ID)
 
-            var latest: Int? = null
-            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.vcnSubId)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -681,7 +658,6 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest).isEqualTo(SUB_1_ID)
-            job.cancel()
         }
 
     @Test
@@ -689,8 +665,7 @@
         testScope.runTest {
             val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
 
-            var latest: Int? = null
-            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.vcnSubId)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -701,14 +676,12 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest).isNull()
-            job.cancel()
         }
 
     @Test
     fun vcnSubId_nullIfNoTransportInfo() =
         testScope.runTest {
-            var latest: Int? = null
-            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.vcnSubId)
 
             val capabilities =
                 mock<NetworkCapabilities>().also {
@@ -719,7 +692,6 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest).isNull()
-            job.cancel()
         }
 
     @Test
@@ -728,8 +700,7 @@
             // If the underlying network of the VCN is a WiFi network, then there is no subId that
             // could disagree with telephony's active data subscription id.
 
-            var latest: Int? = null
-            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.vcnSubId)
 
             val wifiInfo = mock<WifiInfo>()
             val vcnInfo = VcnTransportInfo(wifiInfo)
@@ -742,14 +713,12 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest).isNull()
-            job.cancel()
         }
 
     @Test
     fun vcnSubId_changingVcnInfoIsTracked() =
         testScope.runTest {
-            var latest: Int? = null
-            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.vcnSubId)
 
             val wifiInfo = mock<WifiInfo>()
             val wifiVcnInfo = VcnTransportInfo(wifiInfo)
@@ -788,8 +757,6 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest).isNull()
-
-            job.cancel()
         }
 
     @Test
@@ -862,6 +829,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
     fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() {
         val underlyingNetwork = mock<Network>()
         val underlyingWifiInfo = mock<WifiInfo>()
@@ -916,6 +884,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
     fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
         val underlyingNetwork = mock<Network>()
         val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
@@ -1076,12 +1045,13 @@
                 testScope.backgroundScope,
                 tunerService,
             )
+        testScope.runCurrent()
     }
 
     private fun getTunable(): TunerService.Tunable {
         val callbackCaptor = argumentCaptor<TunerService.Tunable>()
         verify(tunerService).addTunable(callbackCaptor.capture(), any())
-        return callbackCaptor.value!!
+        return callbackCaptor.firstValue
     }
 
     private fun setUpEthernetWifiMobileSlotNames() {
@@ -1094,7 +1064,7 @@
     private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
-        return callbackCaptor.value!!
+        return callbackCaptor.firstValue
     }
 
     private companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 12cfdcf..e396b56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.ui.viewmodel
 
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -33,7 +33,6 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.policy.BatteryController
@@ -127,7 +126,7 @@
         }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     fun isVisible_headsUpStatusBarShown_false() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
index 78028f8..26f6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -3,7 +3,6 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.google.common.truth.Truth.assertThat
@@ -28,7 +27,6 @@
 
     @Test
     fun getSelectedUserIdReturnsId() {
-        mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER)
         runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
 
         val actualId = underTest.getSelectedUserId()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
index dd78e4a..c140364 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
@@ -19,16 +19,17 @@
 import android.media.IVolumeController
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.settingslib.media.data.repository.VolumeControllerEvent
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioRepository
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.filterNotNull
 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.kotlin.eq
@@ -38,14 +39,20 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-class VolumeControllerCollectorTest : SysuiTestCase() {
+class VolumeControllerAdapterTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val eventsFlow = MutableStateFlow<VolumeControllerEvent?>(null)
-    private val underTest = VolumeControllerCollector(kosmos.applicationCoroutineScope)
+    private val underTest =
+        with(kosmos) { VolumeControllerAdapter(applicationCoroutineScope, audioRepository) }
 
     private val volumeController = mock<IVolumeController> {}
 
+    @Before
+    fun setUp() {
+        kosmos.audioRepository.init()
+    }
+
     @Test
     fun volumeControllerEvent_volumeChanged_callsMethod() =
         testEvent(VolumeControllerEvent.VolumeChanged(3, 0)) {
@@ -90,7 +97,8 @@
 
     private fun testEvent(event: VolumeControllerEvent, verify: () -> Unit) =
         kosmos.testScope.runTest {
-            underTest.collectToController(eventsFlow.filterNotNull(), volumeController)
+            kosmos.audioRepository.sendVolumeControllerEvent(event)
+            underTest.collectToController(volumeController)
 
             eventsFlow.value = event
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 4ea1a0c..f62beeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -48,9 +48,11 @@
 
 import com.android.settingslib.flags.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.kosmos.Kosmos;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -78,6 +80,8 @@
 @TestableLooper.RunWithLooper
 public class VolumeDialogControllerImplTest extends SysuiTestCase {
 
+    private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
     TestableVolumeDialogControllerImpl mVolumeController;
     VolumeDialogControllerImpl.C mCallback;
     @Mock
@@ -146,6 +150,7 @@
                         mNotificationManager,
                         mVibrator,
                         mIAudioService,
+                        VolumeControllerAdapterKosmosKt.getVolumeControllerAdapter(mKosmos),
                         mAccessibilityManager,
                         mPackageManager,
                         mWakefullnessLifcycle,
@@ -323,6 +328,7 @@
                 NotificationManager notificationManager,
                 VibratorHelper optionalVibrator,
                 IAudioService iAudioService,
+                VolumeControllerAdapter volumeControllerAdapter,
                 AccessibilityManager accessibilityManager,
                 PackageManager packageManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
@@ -342,6 +348,7 @@
                     notificationManager,
                     optionalVibrator,
                     iAudioService,
+                    volumeControllerAdapter,
                     accessibilityManager,
                     packageManager,
                     wakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt
new file mode 100644
index 0000000..98cea9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.volume
+
+import android.app.activityManager
+import android.app.keyguardManager
+import android.content.applicationContext
+import android.content.packageManager
+import android.media.AudioManager
+import android.media.IVolumeController
+import android.os.Handler
+import android.os.looper
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.TestableLooper
+import android.view.accessibility.accessibilityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.RingerModeLiveData
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.FakeThreadFactory
+import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class VolumeDialogControllerImplTestKt : SysuiTestCase() {
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val kosmos: Kosmos = testKosmos()
+    private val audioManager: AudioManager = mock {}
+    private val callbacks: VolumeDialogController.Callbacks = mock {}
+
+    private lateinit var threadFactory: FakeThreadFactory
+    private lateinit var underTest: VolumeDialogControllerImpl
+
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            audioRepository.init()
+            threadFactory =
+                FakeThreadFactory(FakeExecutor(fakeSystemClock)).apply { setLooper(looper) }
+            underTest =
+                VolumeDialogControllerImpl(
+                        applicationContext,
+                        mock {},
+                        mock {
+                            on { ringerMode }.thenReturn(mock<RingerModeLiveData> {})
+                            on { ringerModeInternal }.thenReturn(mock<RingerModeLiveData> {})
+                        },
+                        threadFactory,
+                        audioManager,
+                        mock {},
+                        mock {},
+                        mock {},
+                        volumeControllerAdapter,
+                        accessibilityManager,
+                        packageManager,
+                        wakefulnessLifecycle,
+                        keyguardManager,
+                        activityManager,
+                        mock { on { userContext }.thenReturn(applicationContext) },
+                        dumpManager,
+                        audioSharingInteractor,
+                        mock {},
+                    )
+                    .apply {
+                        setEnableDialogs(true, true)
+                        addCallback(callbacks, Handler(looper))
+                    }
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_VOLUME_CONTROLLER)
+    fun useVolumeControllerEnabled_listensToVolumeController() =
+        testVolumeController { stream: Int, flags: Int ->
+            audioRepository.sendVolumeControllerEvent(
+                VolumeControllerEvent.VolumeChanged(streamType = stream, flags = flags)
+            )
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_USE_VOLUME_CONTROLLER)
+    fun useVolumeControllerDisabled_listensToVolumeController() =
+        testVolumeController { stream: Int, flags: Int ->
+            audioManager.emitVolumeChange(stream, flags)
+        }
+
+    private fun testVolumeController(
+        emitVolumeChange: suspend Kosmos.(stream: Int, flags: Int) -> Unit
+    ) =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(wakefulnessLifecycle.wakefulness)
+                    .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
+                underTest.setVolumeController()
+                runCurrent()
+
+                emitVolumeChange(AudioManager.STREAM_SYSTEM, AudioManager.FLAG_SHOW_UI)
+                runCurrent()
+                TestableLooper.get(this@VolumeDialogControllerImplTestKt).processAllMessages()
+
+                verify(callbacks) { 1 * { onShowRequested(any(), any(), any()) } }
+            }
+        }
+
+    private companion object {
+
+        private fun AudioManager.emitVolumeChange(stream: Int, flags: Int = 0) {
+            val captor = argumentCaptor<IVolumeController>()
+            verify(this) { 1 * { volumeController = captor.capture() } }
+            captor.firstValue.volumeChanged(stream, flags)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index caa1779..1e2648b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -257,13 +257,13 @@
 
     private State createShellState() {
         State state = new VolumeDialogController.State();
-        for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) {
+        for (int stream : STREAMS.keySet()) {
             VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
-            ss.name = STREAMS.get(i);
+            ss.name = STREAMS.get(stream);
             ss.level = 1;
             ss.levelMin = 0;
             ss.levelMax = 25;
-            state.states.append(i, ss);
+            state.states.append(stream, ss);
         }
         return state;
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 88ab170..811c653 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.education.domain.interactor
 
+import android.hardware.input.InputManager
 import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
 import com.android.systemui.keyboard.data.repository.keyboardRepository
@@ -24,6 +25,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.touchpad.data.repository.touchpadRepository
 import com.android.systemui.user.data.repository.userRepository
+import org.mockito.kotlin.mock
 
 var Kosmos.keyboardTouchpadEduInteractor by
     Kosmos.Fixture {
@@ -37,10 +39,13 @@
                     touchpadRepository,
                     userRepository
                 ),
-            clock = fakeEduClock
+            clock = fakeEduClock,
+            inputManager = mockEduInputManager
         )
     }
 
+var Kosmos.mockEduInputManager by Kosmos.Fixture { mock<InputManager>() }
+
 var Kosmos.keyboardTouchpadEduStatsInteractor by
     Kosmos.Fixture {
         KeyboardTouchpadEduStatsInteractorImpl(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 1107971..c252924 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
-import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
 import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 
@@ -35,7 +35,7 @@
     FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
     FLAG_KEYGUARD_WM_STATE_REFACTOR,
     FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
-    FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
+    FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN,
     FLAG_PREDICTIVE_BACK_SYSUI,
     FLAG_SCENE_CONTAINER,
     FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
new file mode 100644
index 0000000..5ad973a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/FakeMSDLPlayer.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl
+
+import com.google.android.msdl.data.model.FeedbackLevel
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import com.google.android.msdl.domain.MSDLPlayer
+
+class FakeMSDLPlayer : MSDLPlayer {
+    var currentFeedbackLevel = FeedbackLevel.DEFAULT
+    var latestTokenPlayed: MSDLToken? = null
+        private set
+
+    var latestPropertiesPlayed: InteractionProperties? = null
+        private set
+
+    override fun getSystemFeedbackLevel(): FeedbackLevel = currentFeedbackLevel
+
+    override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
+        latestTokenPlayed = token
+        latestPropertiesPlayed = properties
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
index d60f14c..f5a05b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.haptics.msdl
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 616f2b6..a73c184 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -102,6 +102,45 @@
     }
 
     /**
+     * Sends the provided [step] and makes sure that all previous [TransitionState]'s are sent when
+     * [fillInSteps] is true. e.g. when a step FINISHED is provided, a step with STARTED and RUNNING
+     * is also sent.
+     */
+    suspend fun sendTransitionSteps(
+        step: TransitionStep,
+        testScope: TestScope,
+        fillInSteps: Boolean = true,
+    ) {
+        if (fillInSteps && step.transitionState != TransitionState.STARTED) {
+            sendTransitionStep(
+                step =
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                        from = step.from,
+                        to = step.to,
+                        value = 0f,
+                    )
+            )
+            testScope.testScheduler.runCurrent()
+
+            if (step.transitionState != TransitionState.RUNNING) {
+                sendTransitionStep(
+                    step =
+                        TransitionStep(
+                            transitionState = TransitionState.RUNNING,
+                            from = step.from,
+                            to = step.to,
+                            value = 0.6f,
+                        )
+                )
+                testScope.testScheduler.runCurrent()
+            }
+        }
+        sendTransitionStep(step = step)
+        testScope.testScheduler.runCurrent()
+    }
+
+    /**
      * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
      *
      * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt
new file mode 100644
index 0000000..82a5311
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DismissKeyguardInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor by
+    Kosmos.Fixture {
+        KeyguardDismissTransitionInteractor(
+            repository = keyguardTransitionRepository,
+            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
+            fromAodTransitionInteractor = fromAodTransitionInteractor,
+            fromAlternateBouncerTransitionInteractor = fromAlternateBouncerTransitionInteractor,
+            fromDozingTransitionInteractor = fromDozingTransitionInteractor,
+            fromOccludedTransitionInteractor = fromOccludedTransitionInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
deleted file mode 100644
index 9b7bca6..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ /dev/null
@@ -1,129 +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.keyguard.domain.interactor
-
-import android.content.Context
-import android.os.Handler
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.time.FakeSystemClock
-import kotlinx.coroutines.test.TestScope
-import org.mockito.Mockito.mock
-
-/**
- * Helper to create a new KeyguardDismissInteractor in a way that doesn't require modifying many
- * tests whenever we add a constructor param.
- */
-object KeyguardDismissInteractorFactory {
-    @JvmOverloads
-    @JvmStatic
-    fun create(
-        context: Context,
-        testScope: TestScope,
-        trustRepository: FakeTrustRepository = FakeTrustRepository(),
-        keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
-        bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
-        keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
-        powerRepository: FakePowerRepository = FakePowerRepository(),
-        userRepository: FakeUserRepository = FakeUserRepository(),
-    ): WithDependencies {
-        val primaryBouncerInteractor =
-            PrimaryBouncerInteractor(
-                bouncerRepository,
-                mock(BouncerView::class.java),
-                mock(Handler::class.java),
-                mock(KeyguardStateController::class.java),
-                mock(KeyguardSecurityModel::class.java),
-                mock(PrimaryBouncerCallbackInteractor::class.java),
-                mock(FalsingCollector::class.java),
-                mock(DismissCallbackRegistry::class.java),
-                context,
-                keyguardUpdateMonitor,
-                trustRepository,
-                testScope.backgroundScope,
-                mock(SelectedUserInteractor::class.java),
-                mock(DeviceEntryFaceAuthInteractor::class.java),
-            )
-        val alternateBouncerInteractor =
-            AlternateBouncerInteractor(
-                mock(StatusBarStateController::class.java),
-                mock(KeyguardStateController::class.java),
-                bouncerRepository,
-                FakeFingerprintPropertyRepository(),
-                FakeBiometricSettingsRepository(),
-                FakeSystemClock(),
-                keyguardUpdateMonitor,
-                { mock(DeviceEntryBiometricsAllowedInteractor::class.java) },
-                { mock(KeyguardInteractor::class.java) },
-                { mock(KeyguardTransitionInteractor::class.java) },
-                { mock(SceneInteractor::class.java) },
-                testScope.backgroundScope,
-            )
-        val powerInteractorWithDeps =
-            PowerInteractorFactory.create(
-                repository = powerRepository,
-            )
-        val selectedUserInteractor = SelectedUserInteractor(repository = userRepository)
-        return WithDependencies(
-            trustRepository = trustRepository,
-            keyguardRepository = keyguardRepository,
-            bouncerRepository = bouncerRepository,
-            keyguardUpdateMonitor = keyguardUpdateMonitor,
-            powerRepository = powerRepository,
-            userRepository = userRepository,
-            interactor =
-                KeyguardDismissInteractor(
-                    trustRepository,
-                    keyguardRepository,
-                    primaryBouncerInteractor,
-                    alternateBouncerInteractor,
-                    powerInteractorWithDeps.powerInteractor,
-                    selectedUserInteractor,
-                ),
-        )
-    }
-
-    data class WithDependencies(
-        val trustRepository: FakeTrustRepository,
-        val keyguardRepository: FakeKeyguardRepository,
-        val bouncerRepository: FakeKeyguardBouncerRepository,
-        val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-        val powerRepository: FakePowerRepository,
-        val userRepository: FakeUserRepository,
-        val interactor: KeyguardDismissInteractor,
-    )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index f33ca95..ace1157 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -20,7 +20,10 @@
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -29,11 +32,14 @@
 val Kosmos.keyguardDismissInteractor by
     Kosmos.Fixture {
         KeyguardDismissInteractor(
-            trustRepository = trustRepository,
+            mainDispatcher = testDispatcher,
+            scope = applicationCoroutineScope,
             keyguardRepository = keyguardRepository,
             primaryBouncerInteractor = primaryBouncerInteractor,
+            selectedUserInteractor = selectedUserInteractor,
+            dismissCallbackRegistry = dismissCallbackRegistry,
+            trustRepository = trustRepository,
             alternateBouncerInteractor = alternateBouncerInteractor,
             powerInteractor = powerInteractor,
-            selectedUserInteractor = selectedUserInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index c6b5ed0..007d229 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -27,7 +27,7 @@
             applicationCoroutineScope,
             keyguardRepository,
             biometricSettingsRepository,
-            keyguardTransitionInteractor,
+            keyguardDismissTransitionInteractor,
             internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index b68d6a0..aa94c36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,13 +26,6 @@
         KeyguardTransitionInteractor(
             scope = applicationCoroutineScope,
             repository = keyguardTransitionRepository,
-            keyguardRepository = keyguardRepository,
-            fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
-            fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
-            fromAodTransitionInteractor = { fromAodTransitionInteractor },
-            fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
-            fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
-            fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
             sceneInteractor = sceneInteractor
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
index a05e606..4196e54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -25,6 +26,7 @@
 
 val Kosmos.occludedToDozingTransitionViewModel by Fixture {
     OccludedToDozingTransitionViewModel(
+        deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
index 4c05939..e66a2be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -21,7 +21,7 @@
 class FakeActivatable(
     private val onActivation: () -> Unit = {},
     private val onDeactivation: () -> Unit = {},
-) : BaseActivatable() {
+) : ExclusiveActivatable() {
     var activationCount = 0
     var cancellationCount = 0
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index 90cd8c7..2eb7ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.lifecycle
 
 import androidx.compose.runtime.getValue
-import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -27,21 +26,31 @@
 class FakeSysUiViewModel(
     private val onActivation: () -> Unit = {},
     private val onDeactivation: () -> Unit = {},
-    private val upstreamFlow: Flow<Boolean> = flowOf(true),
-    private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
-) : SysUiViewModel() {
+    upstreamFlow: Flow<Boolean> = flowOf(true),
+    upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(),
+) : ExclusiveActivatable() {
 
     var activationCount = 0
     var cancellationCount = 0
 
-    val stateBackedByFlow: Boolean by hydratedStateOf(initialValue = true, source = upstreamFlow)
-    val stateBackedByStateFlow: Boolean by hydratedStateOf(source = upstreamStateFlow)
+    private val hydrator = Hydrator("test")
+    val stateBackedByFlow: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "test",
+            initialValue = true,
+            source = upstreamFlow,
+        )
+    val stateBackedByStateFlow: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "test",
+            source = upstreamStateFlow,
+        )
 
     override suspend fun onActivated(): Nothing {
         activationCount++
         onActivation()
         try {
-            awaitCancellation()
+            hydrator.activate()
         } finally {
             cancellationCount++
             onDeactivation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
index 1473184..61d5f1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.media.controls.util.mediaUiEventLogger
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.notificationLockscreenUserManager
-import com.android.systemui.util.time.systemClock
+import com.android.systemui.util.time.fakeSystemClock
 import com.android.systemui.util.wakelock.WakeLockFake
 
 val Kosmos.mediaDataFilter by
@@ -42,7 +42,7 @@
                 ),
             lockscreenUserManager = notificationLockscreenUserManager,
             executor = fakeExecutor,
-            systemClock = systemClock,
+            systemClock = fakeSystemClock,
             logger = mediaUiEventLogger,
             mediaFlags = mediaFlags,
             mediaFilterRepository = mediaFilterRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 2127a88..632436a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -18,7 +18,6 @@
 
 import android.app.smartspace.SmartspaceManager
 import android.content.applicationContext
-import android.os.fakeExecutorHandler
 import com.android.keyguard.keyguardUpdateMonitor
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.concurrency.fakeExecutor
@@ -45,7 +44,7 @@
             backgroundExecutor = fakeExecutor,
             uiExecutor = fakeExecutor,
             foregroundExecutor = fakeExecutor,
-            handler = fakeExecutorHandler,
+            mainDispatcher = testDispatcher,
             mediaControllerFactory = fakeMediaControllerFactory,
             broadcastDispatcher = broadcastDispatcher,
             dumpManager = dumpManager,
@@ -60,5 +59,6 @@
             smartspaceManager = SmartspaceManager(applicationContext),
             keyguardUpdateMonitor = keyguardUpdateMonitor,
             mediaDataRepository = mediaDataRepository,
+            mediaDataLoader = { mediaDataLoader },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
index d60f14c..76d71dd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceLoggerKosmos.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.media.controls.domain.pipeline
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+var Kosmos.mediaDeviceLogger by
+    Kosmos.Fixture { MediaDeviceLogger(logcatLogBuffer("MediaDeviceLoggerKosmos")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index c479ce6..11408d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -41,5 +41,6 @@
             },
             fgExecutor = fakeExecutor,
             bgExecutor = fakeExecutor,
+            logger = mediaDeviceLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index dd93141..7dfe802 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -1,10 +1,12 @@
 package com.android.systemui.scene
 
+import com.android.compose.animation.scene.OverlayKey
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.shared.model.FakeScene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.FakeOverlay
 
 var Kosmos.sceneKeys by Fixture {
     listOf(
@@ -22,6 +24,17 @@
 val Kosmos.scenes by Fixture { fakeScenes }
 
 val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
+
+var Kosmos.overlayKeys by Fixture {
+    listOf<OverlayKey>(
+        // TODO(b/356596436): Add overlays here when we have them.
+    )
+}
+
+val Kosmos.fakeOverlays by Fixture { overlayKeys.map { key -> FakeOverlay(key) }.toSet() }
+
+val Kosmos.overlays by Fixture { fakeOverlays }
+
 var Kosmos.sceneContainerConfig by Fixture {
     val navigationDistances =
         mapOf(
@@ -32,5 +45,11 @@
             Scenes.QuickSettings to 3,
             Scenes.Bouncer to 4,
         )
-    SceneContainerConfig(sceneKeys, initialSceneKey, navigationDistances)
+
+    SceneContainerConfig(
+        sceneKeys = sceneKeys,
+        initialSceneKey = initialSceneKey,
+        overlayKeys = overlayKeys,
+        navigationDistances = navigationDistances,
+    )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
index 53d3c01..59f2b94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -19,6 +19,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
@@ -36,20 +37,26 @@
 suspend fun Kosmos.setTransition(
     sceneTransition: ObservableTransitionState,
     stateTransition: TransitionStep? = null,
+    fillInStateSteps: Boolean = true,
     scope: TestScope = testScope,
     repository: SceneContainerRepository = sceneContainerRepository
 ) {
+    var state: TransitionStep? = stateTransition
     if (SceneContainerFlag.isEnabled) {
         setSceneTransition(sceneTransition, scope, repository)
-    } else {
-        if (stateTransition == null) throw IllegalArgumentException("No transitionStep provided")
-        fakeKeyguardTransitionRepository.sendTransitionSteps(
-            from = stateTransition.from,
-            to = stateTransition.to,
-            testScope = scope,
-            throughTransitionState = stateTransition.transitionState
-        )
+
+        if (state != null) {
+            state = getStateWithUndefined(sceneTransition, state)
+        }
     }
+
+    if (state == null) return
+    fakeKeyguardTransitionRepository.sendTransitionSteps(
+        step = state,
+        testScope = scope,
+        fillInSteps = fillInStateSteps,
+    )
+    scope.testScheduler.runCurrent()
 }
 
 fun Kosmos.setSceneTransition(
@@ -59,7 +66,7 @@
 ) {
     repository.setTransitionState(mutableTransitionState)
     mutableTransitionState.value = transition
-    scope.runCurrent()
+    scope.testScheduler.runCurrent()
 }
 
 fun Transition(
@@ -87,3 +94,43 @@
 fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle {
     return ObservableTransitionState.Idle(currentScene)
 }
+
+private fun getStateWithUndefined(
+    sceneTransition: ObservableTransitionState,
+    state: TransitionStep
+): TransitionStep {
+    return when (sceneTransition) {
+        is ObservableTransitionState.Idle -> {
+            TransitionStep(
+                from = state.from,
+                to =
+                    if (sceneTransition.currentScene != Scenes.Lockscreen) {
+                        KeyguardState.UNDEFINED
+                    } else {
+                        state.to
+                    },
+                value = state.value,
+                transitionState = state.transitionState
+            )
+        }
+        is ObservableTransitionState.Transition -> {
+            TransitionStep(
+                from =
+                    if (sceneTransition.fromScene != Scenes.Lockscreen) {
+                        KeyguardState.UNDEFINED
+                    } else {
+                        state.from
+                    },
+                to =
+                    if (sceneTransition.toScene != Scenes.Lockscreen) {
+                        KeyguardState.UNDEFINED
+                    } else {
+                        state.from
+                    },
+                value = state.value,
+                transitionState = state.transitionState
+            )
+        }
+        else -> state
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index 64e3526..78358f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,6 +19,8 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onStart
@@ -26,7 +28,7 @@
 
 class FakeScene(
     override val key: SceneKey,
-) : Scene {
+) : ExclusiveActivatable(), Scene {
     var isDestinationScenesBeingCollected = false
 
     private val destinationScenesChannel = Channel<Map<UserAction, UserActionResult>>()
@@ -37,6 +39,10 @@
             .onStart { isDestinationScenesBeingCollected = true }
             .onCompletion { isDestinationScenesBeingCollected = false }
 
+    override suspend fun onActivated(): Nothing {
+        awaitCancellation()
+    }
+
     suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
         destinationScenesChannel.send(value)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index 957a60f..f52572a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.shared.model
 
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,11 +30,18 @@
     private val _currentScene = MutableStateFlow(initialSceneKey)
     override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
 
+    private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet())
+    override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow()
+
     var isPaused = false
         private set
+
     var pendingScene: SceneKey? = null
         private set
 
+    var pendingOverlays: Set<OverlayKey>? = null
+        private set
+
     override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
         if (isPaused) {
             pendingScene = toScene
@@ -46,10 +54,32 @@
         changeScene(toScene)
     }
 
+    override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        if (isPaused) {
+            pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay
+        } else {
+            _currentOverlays.value += overlay
+        }
+    }
+
+    override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
+        if (isPaused) {
+            pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay
+        } else {
+            _currentOverlays.value -= overlay
+        }
+    }
+
+    override fun replaceOverlay(from: OverlayKey, to: OverlayKey, transitionKey: TransitionKey?) {
+        hideOverlay(from, transitionKey)
+        showOverlay(to, transitionKey)
+    }
+
     /**
-     * Pauses scene changes.
+     * Pauses scene and overlay changes.
      *
-     * Any following calls to [changeScene] will be conflated and the last one will be remembered.
+     * Any following calls to [changeScene] or overlay changing functions will be conflated and the
+     * last one will be remembered.
      */
     fun pause() {
         check(!isPaused) { "Can't pause what's already paused!" }
@@ -58,11 +88,14 @@
     }
 
     /**
-     * Unpauses scene changes.
+     * Unpauses scene and overlay changes.
      *
      * If there were any calls to [changeScene] since [pause] was called, the latest of the bunch
      * will be replayed.
      *
+     * If there were any calls to show, hide or replace overlays since [pause] was called, they will
+     * all be applied at once.
+     *
      * If [force] is `true`, there will be no check that [isPaused] is true.
      *
      * If [expectedScene] is provided, will assert that it's indeed the latest called.
@@ -76,6 +109,8 @@
         isPaused = false
         pendingScene?.let { _currentScene.value = it }
         pendingScene = null
+        pendingOverlays?.let { _currentOverlays.value = it }
+        pendingOverlays = null
 
         check(expectedScene == null || currentScene.value == expectedScene) {
             """
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/FakeOverlay.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/FakeOverlay.kt
new file mode 100644
index 0000000..f4f30cd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/FakeOverlay.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.OverlayKey
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.ui.composable.Overlay
+import kotlinx.coroutines.awaitCancellation
+
+class FakeOverlay(
+    override val key: OverlayKey,
+) : ExclusiveActivatable(), Overlay {
+
+    @Composable override fun ContentScope.Content(modifier: Modifier) = Unit
+
+    override suspend fun onActivated(): Nothing {
+        awaitCancellation()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
index 6252d44..4b42e07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 
@@ -24,6 +23,5 @@
     Kosmos.Fixture {
         NotificationShadeWindowModel(
             keyguardTransitionInteractor,
-            keyguardInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
new file mode 100644
index 0000000..c0d65a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
+
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by
+    Kosmos.Fixture {
+        DemoRonChipViewModel(
+            commandRegistry = commandRegistry,
+            packageManager = packageManager,
+            systemClock = fakeSystemClock,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index 16e288f..5382c1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel
 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.statusBarChipsLogger
@@ -32,6 +33,7 @@
             shareToAppChipViewModel = shareToAppChipViewModel,
             castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel,
             callChipViewModel = callChipViewModel,
+            demoRonChipViewModel = demoRonChipViewModel,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
index d60f14c..14777b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
@@ -14,10 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.statusbar.commandline
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.commandRegistry: CommandRegistry by
+    Kosmos.Fixture {
+        CommandRegistry(
+            context = applicationContext,
+            // Immediately run anything that comes in
+            mainExecutor = { command -> command.run() },
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 4dd3ae7..2eb1573 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -35,7 +35,9 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.media.controls.util.MediaFeatureFlag
 import com.android.systemui.media.dialog.MediaOutputDialogManager
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper
@@ -46,6 +48,7 @@
 import com.android.systemui.statusbar.RankingBuilder
 import com.android.systemui.statusbar.SmartReplyController
 import com.android.systemui.statusbar.notification.ColorUpdateLogger
+import com.android.systemui.statusbar.notification.ConversationNotificationManager
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -69,6 +72,7 @@
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
 import com.android.systemui.statusbar.policy.SmartReplyConstants
@@ -84,6 +88,7 @@
 import com.android.systemui.wmshell.BubblesManager
 import java.util.Optional
 import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.test.TestScope
 import org.junit.Assert.assertTrue
@@ -128,19 +133,19 @@
         dependency.injectMockDependency(NotificationShadeWindowController::class.java)
         dependency.injectMockDependency(MediaOutputDialogManager::class.java)
 
-        mMockLogger = Mockito.mock(ExpandableNotificationRowLogger::class.java)
-        mStatusBarStateController = Mockito.mock(StatusBarStateController::class.java)
-        mKeyguardBypassController = Mockito.mock(KeyguardBypassController::class.java)
+        mMockLogger = Mockito.mock(ExpandableNotificationRowLogger::class.java, STUB_ONLY)
+        mStatusBarStateController = Mockito.mock(StatusBarStateController::class.java, STUB_ONLY)
+        mKeyguardBypassController = Mockito.mock(KeyguardBypassController::class.java, STUB_ONLY)
         mGroupMembershipManager = GroupMembershipManagerImpl()
-        mSmartReplyController = Mockito.mock(SmartReplyController::class.java)
+        mSmartReplyController = Mockito.mock(SmartReplyController::class.java, STUB_ONLY)
 
         val dumpManager = DumpManager()
         mGroupExpansionManager = GroupExpansionManagerImpl(dumpManager, mGroupMembershipManager)
-        mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java)
+        mHeadsUpManager = Mockito.mock(HeadsUpManager::class.java, STUB_ONLY)
         mIconManager =
             IconManager(
-                Mockito.mock(CommonNotifCollection::class.java),
-                Mockito.mock(LauncherApps::class.java),
+                Mockito.mock(CommonNotifCollection::class.java, STUB_ONLY),
+                Mockito.mock(LauncherApps::class.java, STUB_ONLY),
                 IconBuilder(context),
                 mTestScope,
                 mBgCoroutineContext,
@@ -173,7 +178,7 @@
                 }
             )
         val remoteViewsFactories = getNotifRemoteViewsFactoryContainer(featureFlags)
-        val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java)
+        val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java, STUB_ONLY)
         val smartReplyStateInflater =
             SmartReplyStateInflaterImpl(
                 constants = mSmartReplyConstants,
@@ -183,7 +188,8 @@
                 smartRepliesInflater =
                     SmartReplyInflaterImpl(
                         constants = mSmartReplyConstants,
-                        keyguardDismissUtil = mock(),
+                        keyguardDismissUtil =
+                            Mockito.mock(KeyguardDismissUtil::class.java, STUB_ONLY),
                         remoteInputManager = remoteInputManager,
                         smartReplyController = mSmartReplyController,
                         context = context
@@ -191,7 +197,7 @@
                 smartActionsInflater =
                     SmartActionInflaterImpl(
                         constants = mSmartReplyConstants,
-                        activityStarter = mock(),
+                        activityStarter = Mockito.mock(ActivityStarter::class.java, STUB_ONLY),
                         smartReplyController = mSmartReplyController,
                         headsUpManager = mHeadsUpManager
                     )
@@ -206,41 +212,42 @@
             }
         val conversationProcessor =
             ConversationNotificationProcessor(
-                mock(),
-                mock(),
+                Mockito.mock(LauncherApps::class.java, STUB_ONLY),
+                Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY),
             )
+
         mContentBinder =
             if (NotificationRowContentBinderRefactor.isEnabled)
                 NotificationRowContentBinderImpl(
-                    mock(),
+                    Mockito.mock(NotifRemoteViewCache::class.java, STUB_ONLY),
                     remoteInputManager,
                     conversationProcessor,
-                    mock(),
-                    mock(),
-                    mock(),
+                    Mockito.mock(RichOngoingNotificationContentExtractor::class.java, STUB_ONLY),
+                    Mockito.mock(RichOngoingNotificationViewInflater::class.java, STUB_ONLY),
+                    Mockito.mock(Executor::class.java, STUB_ONLY),
                     smartReplyStateInflater,
                     notifLayoutInflaterFactoryProvider,
-                    mock(),
-                    mock(),
+                    Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+                    Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
                 )
             else
                 NotificationContentInflater(
-                    mock(),
+                    Mockito.mock(NotifRemoteViewCache::class.java, STUB_ONLY),
                     remoteInputManager,
                     conversationProcessor,
-                    mock(),
-                    mock(),
+                    Mockito.mock(MediaFeatureFlag::class.java, STUB_ONLY),
+                    Mockito.mock(Executor::class.java, STUB_ONLY),
                     smartReplyStateInflater,
                     notifLayoutInflaterFactoryProvider,
-                    mock(),
-                    mock(),
+                    Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+                    Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
                 )
         mContentBinder.setInflateSynchronously(true)
         mBindStage =
             RowContentBindStage(
                 mContentBinder,
-                mock(),
-                mock(),
+                Mockito.mock(NotifInflationErrorManager::class.java, STUB_ONLY),
+                Mockito.mock(RowContentBindStageLogger::class.java, STUB_ONLY),
             )
 
         val collection = Mockito.mock(CommonNotifCollection::class.java)
@@ -248,7 +255,7 @@
         mBindPipeline =
             NotifBindPipeline(
                 collection,
-                Mockito.mock(NotifBindPipelineLogger::class.java),
+                Mockito.mock(NotifBindPipelineLogger::class.java, STUB_ONLY),
                 NotificationEntryProcessorFactoryExecutorImpl(mMainExecutor),
             )
         mBindPipeline.setStage(mBindStage)
@@ -256,9 +263,11 @@
         val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
         Mockito.verify(collection).addCollectionListener(collectionListenerCaptor.capture())
         mBindPipelineEntryListener = collectionListenerCaptor.value
-        mPeopleNotificationIdentifier = Mockito.mock(PeopleNotificationIdentifier::class.java)
+        mPeopleNotificationIdentifier =
+            Mockito.mock(PeopleNotificationIdentifier::class.java, STUB_ONLY)
         mOnUserInteractionCallback = Mockito.mock(OnUserInteractionCallback::class.java)
-        mDismissibilityProvider = Mockito.mock(NotificationDismissibilityProvider::class.java)
+        mDismissibilityProvider =
+            Mockito.mock(NotificationDismissibilityProvider::class.java, STUB_ONLY)
         val mFutureDismissalRunnable = Mockito.mock(Runnable::class.java)
         whenever(
                 mOnUserInteractionCallback.registerFutureDismissal(
@@ -320,7 +329,10 @@
         //  set, but we do not want to override an existing value that is needed by a specific test.
 
         val rowInflaterTask =
-            RowInflaterTask(mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java))
+            RowInflaterTask(
+                mFakeSystemClock,
+                Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY)
+            )
         val row = rowInflaterTask.inflateSynchronously(context, null, entry)
 
         entry.row = row
@@ -329,7 +341,7 @@
         mBindPipeline.manageRow(entry, row)
         row.initialize(
             entry,
-            Mockito.mock(RemoteInputViewSubcomponent.Factory::class.java),
+            Mockito.mock(RemoteInputViewSubcomponent.Factory::class.java, STUB_ONLY),
             APP_NAME,
             entry.key,
             mMockLogger,
@@ -338,23 +350,23 @@
             mGroupExpansionManager,
             mHeadsUpManager,
             mBindStage,
-            Mockito.mock(OnExpandClickListener::class.java),
-            Mockito.mock(CoordinateOnClickListener::class.java),
+            Mockito.mock(OnExpandClickListener::class.java, STUB_ONLY),
+            Mockito.mock(CoordinateOnClickListener::class.java, STUB_ONLY),
             FalsingManagerFake(),
             mStatusBarStateController,
             mPeopleNotificationIdentifier,
             mOnUserInteractionCallback,
-            Optional.of(Mockito.mock(BubblesManager::class.java)),
-            Mockito.mock(NotificationGutsManager::class.java),
+            Optional.of(Mockito.mock(BubblesManager::class.java, STUB_ONLY)),
+            Mockito.mock(NotificationGutsManager::class.java, STUB_ONLY),
             mDismissibilityProvider,
-            Mockito.mock(MetricsLogger::class.java),
-            Mockito.mock(NotificationChildrenContainerLogger::class.java),
-            Mockito.mock(ColorUpdateLogger::class.java),
+            Mockito.mock(MetricsLogger::class.java, STUB_ONLY),
+            Mockito.mock(NotificationChildrenContainerLogger::class.java, STUB_ONLY),
+            Mockito.mock(ColorUpdateLogger::class.java, STUB_ONLY),
             mSmartReplyConstants,
             mSmartReplyController,
             featureFlags,
-            Mockito.mock(IStatusBarService::class.java),
-            Mockito.mock(UiEventLogger::class.java)
+            Mockito.mock(IStatusBarService::class.java, STUB_ONLY),
+            Mockito.mock(UiEventLogger::class.java, STUB_ONLY)
         )
         row.setAboveShelfChangedListener { aboveShelf: Boolean -> }
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags)
@@ -381,6 +393,8 @@
         private val Notification.isConversationStyleNotification
             get() = extras.getBoolean(IS_CONVERSATION_FLAG, false)
 
+        private val STUB_ONLY = Mockito.withSettings().stubOnly()
+
         fun markAsConversation(builder: Notification.Builder) {
             builder.addExtras(bundleOf(IS_CONVERSATION_FLAG to true))
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
new file mode 100644
index 0000000..7e51135
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository
+import com.android.systemui.statusbar.notification.row.domain.interactor.getNotificationRowInteractor
+
+fun Kosmos.getEnRouteViewModel(repository: NotificationRowRepository) =
+    EnRouteViewModel(
+        dumpManager = dumpManager,
+        rowInteractor = getNotificationRowInteractor(repository),
+    )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt
index dbfd9de..2772d36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.data.repository.notificationPlaceholderRepository
 import com.android.systemui.statusbar.notification.stack.data.repository.notificationViewHeightRepository
@@ -26,6 +27,7 @@
     NotificationStackAppearanceInteractor(
         viewHeightRepository = notificationViewHeightRepository,
         placeholderRepository = notificationPlaceholderRepository,
+        sceneInteractor = sceneInteractor,
         shadeInteractor = shadeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/leak/ReferenceTestUtils.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/leak/ReferenceTestUtils.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index a8328e4..2dbac67 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -14,8 +14,11 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.graphics.drawable.Drawable;
 import android.testing.LeakCheck;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.ui.IconManager;
@@ -60,6 +63,11 @@
     }
 
     @Override
+    public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId,
+            @Nullable Drawable preloadedIcon, CharSequence contentDescription) {
+    }
+
+    @Override
     public void setNewWifiIcon() {
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt
similarity index 79%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt
index d60f14c..4045135b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.data.repository.audioRepository
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.volumeControllerAdapter by
+    Kosmos.Fixture { VolumeControllerAdapter(applicationCoroutineScope, audioRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 135cb14..1fa6c3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -18,12 +18,16 @@
 
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
 
@@ -39,10 +43,26 @@
     override val communicationDevice: StateFlow<AudioDeviceInfo?> =
         mutableCommunicationDevice.asStateFlow()
 
+    private val mutableVolumeControllerEvents = MutableSharedFlow<VolumeControllerEvent>(replay = 1)
+    override val volumeControllerEvents: Flow<VolumeControllerEvent>
+        get() = mutableVolumeControllerEvents.asSharedFlow()
+
     private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
     private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
     private val deviceCategories: MutableMap<String, Int> = mutableMapOf()
 
+    private val mutableIsVolumeControllerVisible = MutableStateFlow(false)
+    val isVolumeControllerVisible: StateFlow<Boolean>
+        get() = mutableIsVolumeControllerVisible.asStateFlow()
+
+    private var mutableIsInitialized: Boolean = false
+    val isInitialized: Boolean
+        get() = mutableIsInitialized
+
+    override fun init() {
+        mutableIsInitialized = true
+    }
+
     private fun getAudioStreamModelState(
         audioStream: AudioStream
     ): MutableStateFlow<AudioStreamModel> =
@@ -111,4 +131,16 @@
     override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
         return deviceCategories[bluetoothAddress] ?: AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN
     }
+
+    suspend fun sendVolumeControllerEvent(event: VolumeControllerEvent) {
+        if (isInitialized) {
+            mutableVolumeControllerEvents.emit(event)
+        }
+    }
+
+    override suspend fun notifyVolumeControllerVisible(isVisible: Boolean) {
+        if (isInitialized) {
+            mutableIsVolumeControllerVisible.value = isVisible
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
index 63386d0..dd5bbf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.volume.data.repository.audioRepository
 import com.android.systemui.volume.domain.interactor.audioModeInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 
@@ -27,7 +26,6 @@
         AudioSlidersInteractor(
             applicationCoroutineScope,
             mediaOutputInteractor,
-            audioRepository,
             audioModeInteractor,
         )
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 09068d5..26b0f61 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2605,11 +2605,7 @@
             ret.size.height = imageReaderOutputConfig.getSize().getHeight();
             ret.imageFormat = imageReaderOutputConfig.getImageFormat();
             ret.capacity = imageReaderOutputConfig.getMaxImages();
-            if (EFV_SUPPORTED) {
-                ret.usage = imageReaderOutputConfig.getUsage();
-            } else {
-                ret.usage = 0;
-            }
+            ret.usage = imageReaderOutputConfig.getUsage();
         } else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) {
             MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig =
                     (MultiResolutionImageReaderOutputConfigImpl) output;
diff --git a/ravenwood/.gitignore b/ravenwood/.gitignore
new file mode 100644
index 0000000..751553b
--- /dev/null
+++ b/ravenwood/.gitignore
@@ -0,0 +1 @@
+*.bak
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index c71b99f..9b0c8e5 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -160,6 +160,7 @@
         "ravenwood-framework",
         "services.core.ravenwood",
         "junit",
+        "framework-annotations-lib",
     ],
     sdk_version: "core_current",
     visibility: ["//frameworks/base"],
@@ -211,6 +212,7 @@
     libs: [
         "junit",
         "flag-junit",
+        "framework-annotations-lib",
     ],
     visibility: ["//visibility:public"],
 }
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 7e2ee3e..b73f235 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,6 +1,3 @@
-// Keep the following two TEST_MAPPINGs in sync:
-// frameworks/base/ravenwood/TEST_MAPPING
-// frameworks/base/tools/hoststubgen/TEST_MAPPING
 {
   "presubmit": [
     { "name": "tiny-framework-dump-test" },
@@ -39,6 +36,113 @@
     }
   ],
   "ravenwood-presubmit": [
+    // AUTO-GENERATED-START
+    // DO NOT MODIFY MANUALLY
+    // Use scripts/update-test-mapping.sh to update it.
+    {
+      "name": "AdServicesSharedLibrariesUnitTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "android.test.mock.ravenwood.tests",
+      "host": true
+    },
+    {
+      "name": "CarLibHostUnitTest",
+      "host": true
+    },
+    {
+      "name": "CarServiceHostUnitTest",
+      "host": true
+    },
+    {
+      "name": "CarSystemUIRavenTests",
+      "host": true
+    },
+    {
+      "name": "CtsAccountManagerTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsAppTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsContentTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsDatabaseTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsGraphicsTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsIcuTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsInputMethodTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsOsTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsProtoTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsResourcesTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsTextTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "CtsUtilTestCasesRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksCoreSystemPropertiesTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksCoreTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksInputMethodSystemServerTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksMockingServicesTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksServicesTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "FrameworksUtilTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "InternalTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "PowerStatsTestsRavenwood",
+      "host": true
+    },
+    {
+      "name": "RavenwoodBivalentTest",
+      "host": true
+    },
     {
       "name": "RavenwoodMinimumTest",
       "host": true
@@ -48,16 +152,21 @@
       "host": true
     },
     {
-      "name": "CtsUtilTestCasesRavenwood",
-      "host": true
-    },
-    {
       "name": "RavenwoodResApkTest",
       "host": true
     },
     {
-      "name": "RavenwoodBivalentTest",
+      "name": "RavenwoodRuntimeTest",
+      "host": true
+    },
+    {
+      "name": "RavenwoodServicesTest",
+      "host": true
+    },
+    {
+      "name": "SystemUiRavenTests",
       "host": true
     }
+    // AUTO-GENERATED-END
   ]
 }
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
index 06cf08e6..e897735 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/bivalenttest/Android.bp
@@ -39,6 +39,9 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
 
+        "junit-params",
+        "platform-parametric-runner-lib",
+
         // To make sure it won't cause VerifyError (b/324063814)
         "platformprotosnano",
     ],
@@ -65,6 +68,9 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
 
+        "junit-params",
+        "platform-parametric-runner-lib",
+
         "ravenwood-junit",
     ],
     jni_libs: [
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
index 3a24c0e..e8f59db 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -26,6 +26,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/**
+ * Test to ensure @DisabledOnRavenwood works. Note, now the DisabledOnRavenwood annotation
+ * is handled by the test runner, so it won't really need the class rule.
+ */
 @RunWith(AndroidJUnit4.class)
 @DisabledOnRavenwood
 public class RavenwoodClassRuleDeviceOnlyTest {
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
new file mode 100644
index 0000000..8dadd39
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/CallTracker.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+
+import android.util.Log;
+
+import java.lang.StackWalker.StackFrame;
+import java.util.HashMap;
+
+/**
+ * Used to keep track of and count the number of calls.
+ */
+public class CallTracker {
+    public static final String TAG = "CallTracker";
+
+    private final HashMap<String, Integer> mNumCalled = new HashMap<>();
+
+    /**
+     * Call it when a method is called. It increments the count for the calling method.
+     */
+    public void incrementMethodCallCount() {
+        var methodName = getCallingMethodName(1);
+
+        Log.i(TAG, "Method called: " + methodName);
+
+        mNumCalled.put(methodName, getNumCalled(methodName) + 1);
+    }
+
+    /**
+     * Return the number of calls of a method.
+     */
+    public int getNumCalled(String methodName) {
+        return mNumCalled.getOrDefault(methodName, 0);
+    }
+
+    /**
+     * Return the current method name. (with the class name.)
+     */
+    private static String getCallingMethodName(int frameOffset) {
+        var walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
+        var caller = walker.walk(frames ->
+                frames.skip(1 + frameOffset).findFirst().map(StackFrame::getMethodName)
+        );
+        return caller.get();
+    }
+
+    /**
+     * Check the number of calls stored in {@link #mNumCalled}.
+     */
+    protected void assertCalls(Object... methodNameAndCountPairs) {
+        // Create a local copy
+        HashMap<String, Integer> counts = new HashMap<>(mNumCalled);
+        for (int i = 0; i < methodNameAndCountPairs.length - 1; i += 2) {
+            String methodName = (String) methodNameAndCountPairs[i];
+            int expectedCount = (Integer) methodNameAndCountPairs[i + 1];
+
+            if (getNumCalled(methodName) != expectedCount) {
+                fail(String.format("Method %s: expected call count=%d, actual=%d",
+                        methodName, expectedCount, getNumCalled(methodName)));
+            }
+            counts.remove(methodName);
+        }
+        // All other entries are expected to be 0.
+        var sb = new StringBuilder();
+        for (var e : counts.entrySet()) {
+            if (e.getValue() == 0) {
+                continue;
+            }
+            sb.append(String.format("Method %s: expected call count=0, actual=%d",
+                    e.getKey(), e.getValue()));
+        }
+        if (sb.length() > 0) {
+            fail(sb.toString());
+        }
+    }
+
+    /**
+     * Same as {@link #assertCalls(Object...)} but it kills the process if it fails.
+     * Only use in @AfterClass.
+     */
+    protected void assertCallsOrDie(Object... methodNameAndCountPairs) {
+        try {
+            assertCalls(methodNameAndCountPairs);
+        } catch (Throwable th) {
+            // TODO: I don't think it's by spec, but the exception here would be ignored both on
+            // ravenwood and on the device side. Look into it.
+            Log.e(TAG, "*** Failure detected in @AfterClass! ***", th);
+            Log.e(TAG, "JUnit seems to ignore exceptions from @AfterClass, so killing self.");
+            System.exit(7);
+        }
+    }
+
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
new file mode 100644
index 0000000..d7c2c6c
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertFalse;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure RavenwoodAwareTestRunnerTest properly delegates to the original runner,
+ * and also run the special annotated methods.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodAwareTestRunnerTest {
+    public static final String TAG = "RavenwoodAwareTestRunnerTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    private static int getExpectedRavenwoodRunnerInitializingNumCalls() {
+        return RavenwoodRule.isOnRavenwood() ? 1 : 0;
+    }
+
+    @RavenwoodTestRunnerInitializing
+    public static void ravenwoodRunnerInitializing() {
+        // No other calls should have been made.
+        sCallTracker.assertCalls();
+
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @BeforeClass
+    public static void beforeClass() {
+        sCallTracker.assertCalls(
+                "ravenwoodRunnerInitializing",
+                getExpectedRavenwoodRunnerInitializingNumCalls()
+        );
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    public void test1() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    @Parameters({"foo", "bar"})
+    public void testWithParams(String arg) {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    @DisabledOnRavenwood
+    public void testDeviceOnly() {
+        assertFalse(RavenwoodRule.isOnRavenwood());
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "ravenwoodRunnerInitializing",
+                getExpectedRavenwoodRunnerInitializingNumCalls(),
+                "beforeClass", 1,
+                "test1", 1,
+                "testWithParams", 2
+        );
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
new file mode 100644
index 0000000..7ef672e
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitClassRuleDeviceOnlyTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodImplicitClassRuleDeviceOnlyTest {
+    public static final String TAG = "RavenwoodImplicitClassRuleDeviceOnlyTest";
+
+    @BeforeClass
+    public static void beforeClass() {
+        // This method shouldn't be called -- unless RUN_DISABLED_TESTS is enabled.
+
+        // If we're doing RUN_DISABLED_TESTS, don't throw here, because that'd confuse junit.
+        if (!RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+            Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+        }
+    }
+
+    @Test
+    public void testDeviceOnly() {
+        Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        if (RavenwoodRule.isOnRavenwood()) {
+            Log.e(TAG, "Even @AfterClass shouldn't be executed!");
+
+            if (!RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+                System.exit(1);
+            }
+        }
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
new file mode 100644
index 0000000..7ef40dc
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleOrderRewriteTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assume;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+
+/**
+ * Make sure ravenizer will inject implicit rules and rewrite the existing rules' orders.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodImplicitRuleOrderRewriteTest {
+
+    private static final TestRule sEmptyRule = (statement, description) -> statement;
+
+    // We have two sets of 9 rules below, for class rules and instance rules.
+    // - Ravenizer will inject 2 more rules of each kind.
+    // - Ravenizer will adjust their order, so even though we'll add two sets of class and instance
+    //  rules with a MIN / MAX order, there will still be no duplicate in the order.
+
+    private static final int EXPECTED_RULE_COUNT = 9 + 2;
+
+    @ClassRule(order = Integer.MIN_VALUE)
+    public static final TestRule sRule01 = sEmptyRule;
+
+    @ClassRule(order = Integer.MIN_VALUE + 1)
+    public static final TestRule sRule02 = sEmptyRule;
+
+    @ClassRule(order = -10)
+    public static final TestRule sRule03 = sEmptyRule;
+
+    @ClassRule(order = -1)
+    public static final TestRule sRule04 = sEmptyRule;
+
+    @ClassRule(order = 0)
+    public static final TestRule sRule05 = sEmptyRule;
+
+    @ClassRule(order = 1)
+    public static final TestRule sRule06 = sEmptyRule;
+
+    @ClassRule(order = 10)
+    public static final TestRule sRule07 = sEmptyRule;
+
+    @ClassRule(order = Integer.MAX_VALUE - 1)
+    public static final TestRule sRule08 = sEmptyRule;
+
+    @ClassRule(order = Integer.MAX_VALUE)
+    public static final TestRule sRule09 = sEmptyRule;
+
+    @Rule(order = Integer.MIN_VALUE)
+    public final TestRule mRule01 = sEmptyRule;
+
+    @Rule(order = Integer.MIN_VALUE + 1)
+    public final TestRule mRule02 = sEmptyRule;
+
+    @Rule(order = -10)
+    public final TestRule mRule03 = sEmptyRule;
+
+    @Rule(order = -1)
+    public final TestRule mRule04 = sEmptyRule;
+
+    @Rule(order = 0)
+    public final TestRule mRule05 = sEmptyRule;
+
+    @Rule(order = 1)
+    public final TestRule mRule06 = sEmptyRule;
+
+    @Rule(order = 10)
+    public final TestRule mRule07 = sEmptyRule;
+
+    @Rule(order = Integer.MAX_VALUE - 1)
+    public final TestRule mRule08 = sEmptyRule;
+
+    @Rule(order = Integer.MAX_VALUE)
+    public final TestRule mRule09 = sEmptyRule;
+
+    private void checkRules(boolean classRule) {
+        final var anotClass = classRule ? ClassRule.class : Rule.class;
+
+        final HashMap<Integer, Integer> ordersUsed = new HashMap<>();
+
+        for (var field : this.getClass().getDeclaredFields()) {
+            if (!field.isAnnotationPresent(anotClass)) {
+                continue;
+            }
+            final var anot = field.getAnnotation(anotClass);
+            final int order = classRule ? ((ClassRule) anot).order() : ((Rule) anot).order();
+
+            if (ordersUsed.containsKey(order)) {
+                fail("Detected duplicate order=" + order);
+            }
+            ordersUsed.put(order, 1);
+        }
+        assertEquals(EXPECTED_RULE_COUNT, ordersUsed.size());
+    }
+
+    @Test
+    public void testClassRules() {
+        Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+        checkRules(true);
+    }
+
+    @Test
+    public void testInstanceRules() {
+        Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+
+        checkRules(false);
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
new file mode 100644
index 0000000..ae596b1
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to make sure when a test class inherits another test class, the base class's
+ * implicit rules are shadowed and won't be executed.
+ *
+ * ... But for now, we don't have a way to programmatically check it, so for now we need to
+ * check the log file manually.
+ *
+ * TODO: Implement the test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodImplicitRuleShadowingTest extends RavenwoodImplicitRuleShadowingTestBase {
+    @Test
+    public void testOkInSubClass() {
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
index d60f14c..1ca97af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodImplicitRuleShadowingTestBase.java
@@ -13,11 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.ravenwoodtest.bivalenttest.ravenizer;
 
-package com.android.systemui.volume
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+/**
+ * A test class that's just inherited by RavenwoodImplicitRuleShadowingTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public abstract class RavenwoodImplicitRuleShadowingTestBase {
+    @Test
+    public void testOkInBaseClass() {
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
new file mode 100644
index 0000000..c77841b
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for "RAVENWOOD_RUN_DISABLED_TESTS" with "REALLY_DISABLED" set.
+ *
+ * This test is only executed on Ravenwood.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunDisabledTestsReallyDisabledTest {
+    private static final String TAG = "RavenwoodRunDisabledTestsTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    @RavenwoodTestRunnerInitializing
+    public static void ravenwoodRunnerInitializing() {
+        RavenwoodRule.private$ravenwood().overrideRunDisabledTest(true,
+                "\\#testReallyDisabled$");
+    }
+
+    /**
+     * This test gets to run with RAVENWOOD_RUN_DISABLED_TESTS set.
+     */
+    @Test
+    @DisabledOnRavenwood
+    public void testDisabledTestGetsToRun() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        sCallTracker.incrementMethodCallCount();
+
+        fail("This test won't pass on Ravenwood.");
+    }
+
+    /**
+     * This will still not be executed due to the "really disabled" pattern.
+     */
+    @Test
+    @DisabledOnRavenwood
+    public void testReallyDisabled() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        sCallTracker.incrementMethodCallCount();
+
+        fail("This test won't pass on Ravenwood.");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "testDisabledTestGetsToRun", 1,
+                "testReallyDisabled", 0
+        );
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
new file mode 100644
index 0000000..ea1a29d
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for "RAVENWOOD_RUN_DISABLED_TESTS". (with no "REALLY_DISABLED" set.)
+ *
+ * This test is only executed on Ravenwood.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunDisabledTestsTest {
+    private static final String TAG = "RavenwoodRunDisabledTestsTest";
+
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    @RavenwoodTestRunnerInitializing
+    public static void ravenwoodRunnerInitializing() {
+        RavenwoodRule.private$ravenwood().overrideRunDisabledTest(true, null);
+    }
+
+    @Test
+    @DisabledOnRavenwood
+    public void testDisabledTestGetsToRun() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        sCallTracker.incrementMethodCallCount();
+
+        fail("This test won't pass on Ravenwood.");
+    }
+
+    @Test
+    @DisabledOnRavenwood
+    public void testDisabledButPass() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        sCallTracker.incrementMethodCallCount();
+
+        // When a @DisabledOnRavenwood actually passed, the runner should make fail().
+        mExpectedException.expectMessage("it actually passed under Ravenwood");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        if (!RavenwoodRule.isOnRavenwood()) {
+            return;
+        }
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "testDisabledTestGetsToRun", 1,
+                "testDisabledButPass", 1
+        );
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
new file mode 100644
index 0000000..c042eb0
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithAndroidXRunnerTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRunnerWithAndroidXRunnerTest {
+    public static final String TAG = "RavenwoodRunnerWithAndroidXRunnerTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    @BeforeClass
+    public static void beforeClass() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Before
+    public void beforeTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @After
+    public void afterTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    public void test1() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    public void test2() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "beforeClass", 1,
+                "beforeTest", 2,
+                "afterTest", 2,
+                "test1", 1,
+                "test2", 1
+        );
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
new file mode 100644
index 0000000..2feb5ba
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithJUnitParamsRunnerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Make sure ravenwood's test runner works with {@link AndroidJUnit4}.
+ */
+@RunWith(JUnitParamsRunner.class)
+public class RavenwoodRunnerWithJUnitParamsRunnerTest  {
+    public static final String TAG = "RavenwoodRunnerTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    @BeforeClass
+    public static void beforeClass() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Before
+    public void beforeTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @After
+    public void afterTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    public void testWithNoParams() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Test
+    @Parameters({"foo", "bar"})
+    public void testWithParams(String arg) {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "beforeClass", 1,
+                "beforeTest", 3,
+                "afterTest", 3,
+                "testWithNoParams", 1,
+                "testWithParams", 2
+        );
+    }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
new file mode 100644
index 0000000..7e3bc0f
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunnerWithParameterizedAndroidJunit4Test.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Make sure ravenwood's test runner works with {@link ParameterizedAndroidJunit4}.
+ */
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodRunnerWithParameterizedAndroidJunit4Test {
+    public static final String TAG = "RavenwoodRunnerTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    private final String mParam;
+
+    private static int sNumInsantiation = 0;
+
+    public RavenwoodRunnerWithParameterizedAndroidJunit4Test(String param) {
+        mParam = param;
+        sNumInsantiation++;
+    }
+
+    @BeforeClass
+    public static void beforeClass() {
+        // It seems like ParameterizedAndroidJunit4 calls the @BeforeTest / @AfterTest methods
+        // one time too many.
+        // With two parameters, this method should be called only twice, but it's actually
+        // called three times.
+        // So let's not check the number fo beforeClass calls.
+    }
+
+    @Before
+    public void beforeTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @After
+    public void afterTest() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @Parameters
+    public static List<String> getParams() {
+        var params =  new ArrayList<String>();
+        params.add("foo");
+        params.add("bar");
+        return params;
+    }
+
+    @Test
+    public void testWithParams() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "beforeTest", sNumInsantiation,
+                "afterTest", sNumInsantiation,
+                "testWithParams", sNumInsantiation
+        );
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 0000000..1da93eb
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,162 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+import android.platform.test.ravenwood.RavenwoodTestStats.Result;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}.
+ */
+public class RavenwoodAwareTestRunnerHook {
+    private static final String TAG = "RavenwoodAwareTestRunnerHook";
+
+    private RavenwoodAwareTestRunnerHook() {
+    }
+
+    private static RavenwoodTestStats sStats; // lazy initialization.
+    private static Description sCurrentClassDescription;
+
+    private static RavenwoodTestStats getStats() {
+        if (sStats == null) {
+            // We don't want to throw in the static initializer, because tradefed may not report
+            // it properly, so we initialize it here.
+            sStats = new RavenwoodTestStats();
+        }
+        return sStats;
+    }
+
+    /**
+     * Called when a runner starts, before the inner runner gets a chance to run.
+     */
+    public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+        // This log call also ensures the framework JNI is loaded.
+        Log.i(TAG, "onRunnerInitializing: testClass=" + testClass + " runner=" + runner);
+
+        // TODO: Move the initialization code to a better place.
+
+        // This will let AndroidJUnit4 use the original runner.
+        System.setProperty("android.junit.runner",
+                "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
+        System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
+
+
+        // This is needed to make AndroidJUnit4ClassRunner happy.
+        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+    }
+
+    /**
+     * Called when a whole test class is skipped.
+     */
+    public static void onClassSkipped(Description description) {
+        Log.i(TAG, "onClassSkipped: description=" + description);
+        getStats().onClassSkipped(description);
+    }
+
+    /**
+     * Called before a test / class.
+     *
+     * Return false if it should be skipped.
+     */
+    public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+            Scope scope, Order order) {
+        Log.i(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
+
+        if (scope == Scope.Class && order == Order.First) {
+            // Keep track of the current class.
+            sCurrentClassDescription = description;
+        }
+
+        // Class-level annotations are checked by the runner already, so we only check
+        // method-level annotations here.
+        if (scope == Scope.Instance && order == Order.First) {
+            if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+                    description, true)) {
+                getStats().onTestFinished(sCurrentClassDescription, description, Result.Skipped);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called after a test / class.
+     *
+     * Return false if the exception should be ignored.
+     */
+    public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
+            Scope scope, Order order, Throwable th) {
+        Log.i(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+
+        if (scope == Scope.Instance && order == Order.First) {
+            getStats().onTestFinished(sCurrentClassDescription, description,
+                    th == null ? Result.Passed : Result.Failed);
+
+        } else if (scope == Scope.Class && order == Order.Last) {
+            getStats().onClassFinished(sCurrentClassDescription);
+        }
+
+        // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
+        if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
+                && scope == Scope.Instance && order == Order.First) {
+
+            boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+                    description, false);
+            if (th == null) {
+                // Test passed. Is the test method supposed to be enabled?
+                if (isTestEnabled) {
+                    // Enabled and didn't throw, okay.
+                    return true;
+                } else {
+                    // Disabled and didn't throw. We should report it.
+                    fail("Test wasn't included under Ravenwood, but it actually "
+                            + "passed under Ravenwood; consider updating annotations");
+                    return true; // unreachable.
+                }
+            } else {
+                // Test failed.
+                if (isTestEnabled) {
+                    // Enabled but failed. We should throw the exception.
+                    return true;
+                } else {
+                    // Disabled and failed. Expected. Don't throw.
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called by {@link RavenwoodAwareTestRunner} to see if it should run a test class or not.
+     */
+    public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
+        return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true);
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
new file mode 100644
index 0000000..77275c4
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
@@ -0,0 +1,117 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+
+import org.junit.runner.Description;
+
+/**
+ * Calculates which tests need to be executed on Ravenwood.
+ */
+public class RavenwoodEnablementChecker {
+    private static final String TAG = "RavenwoodDisablementChecker";
+
+    private RavenwoodEnablementChecker() {
+    }
+
+    /**
+     * Determine if the given {@link Description} should be enabled when running on the
+     * Ravenwood test environment.
+     *
+     * A more specific method-level annotation always takes precedence over any class-level
+     * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+     * an {@link DisabledOnRavenwood} annotation.
+     */
+    public static boolean shouldEnableOnRavenwood(Description description,
+            boolean takeIntoAccountRunDisabledTestsFlag) {
+        // First, consult any method-level annotations
+        if (description.isTest()) {
+            Boolean result = null;
+
+            // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+            if (description.getMethodName().endsWith("$noRavenwood")) {
+                result = false;
+            } else if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
+                result = true;
+            } else if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
+                result = false;
+            } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                result = false;
+            }
+            if (result != null) {
+                if (takeIntoAccountRunDisabledTestsFlag
+                        && RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+                    result = !shouldStillIgnoreInProbeIgnoreMode(
+                            description.getTestClass(), description.getMethodName());
+                }
+            }
+            if (result != null) {
+                return result;
+            }
+        }
+
+        // Otherwise, consult any class-level annotations
+        return shouldRunClassOnRavenwood(description.getTestClass(),
+                takeIntoAccountRunDisabledTestsFlag);
+    }
+
+    public static boolean shouldRunClassOnRavenwood(@NonNull Class<?> testClass,
+            boolean takeIntoAccountRunDisabledTestsFlag) {
+        boolean result = true;
+        if (testClass.getAnnotation(EnabledOnRavenwood.class) != null) {
+            result = true;
+        } else if (testClass.getAnnotation(DisabledOnRavenwood.class) != null) {
+            result = false;
+        } else if (testClass.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            result = false;
+        }
+        if (!result) {
+            if (takeIntoAccountRunDisabledTestsFlag
+                    && RavenwoodRule.private$ravenwood().isRunningDisabledTests()) {
+                result = !shouldStillIgnoreInProbeIgnoreMode(testClass, null);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Check if a test should _still_ disabled even if {@code RUN_DISABLED_TESTS}
+     * is true, using {@code REALLY_DISABLED_PATTERN}.
+     *
+     * This only works on tests, not on classes.
+     */
+    static boolean shouldStillIgnoreInProbeIgnoreMode(
+            @NonNull Class<?> testClass, @Nullable String methodName) {
+        if (RavenwoodRule.private$ravenwood().getReallyDisabledPattern().pattern().isEmpty()) {
+            return false;
+        }
+
+        final var fullname = testClass.getName() + (methodName != null ? "#" + methodName : "");
+
+        System.out.println("XXX=" + fullname);
+
+        if (RavenwoodRule.private$ravenwood().getReallyDisabledPattern().matcher(fullname).find()) {
+            System.out.println("Still ignoring " + fullname);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 7b4c173..a2088fd 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -40,7 +40,6 @@
 import com.android.server.LocalServices;
 
 import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 
 import java.io.File;
 import java.io.IOException;
@@ -226,11 +225,6 @@
         }
     }
 
-    public static void validate(Statement base, Description description,
-            boolean enableOptionalValidation) {
-        // Nothing to check, for now.
-    }
-
     /**
      * Set the current configuration to the actual SystemProperties.
      */
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
new file mode 100644
index 0000000..631f68f
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -0,0 +1,163 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.util.Log;
+
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Creats a "stats" CSV file containing the test results.
+ *
+ * The output file is created as `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_[TIMESTAMP].csv`.
+ * A symlink to the latest result will be created as
+ * `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_latest.csv`.
+ */
+public class RavenwoodTestStats {
+    private static final String TAG = "RavenwoodTestStats";
+    private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+
+    public enum Result {
+        Passed,
+        Failed,
+        Skipped,
+    }
+
+    private final File mOutputFile;
+    private final PrintWriter mOutputWriter;
+    private final String mTestModuleName;
+
+    public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+
+    /** Ctor */
+    public RavenwoodTestStats() {
+        mTestModuleName = guessTestModuleName();
+
+        var basename = "Ravenwood-stats_" + mTestModuleName + "_";
+
+        // Get the current time
+        LocalDateTime now = LocalDateTime.now();
+        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
+
+        var tmpdir = System.getProperty("java.io.tmpdir");
+        mOutputFile = new File(tmpdir, basename + now.format(fmt) + ".csv");
+
+        try {
+            mOutputWriter = new PrintWriter(mOutputFile);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+        }
+
+        // Crete the "latest" symlink.
+        Path symlink = Paths.get(tmpdir, basename + "latest.csv");
+        try {
+            if (Files.exists(symlink)) {
+                Files.delete(symlink);
+            }
+            Files.createSymbolicLink(symlink, Paths.get(mOutputFile.getName()));
+
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+        }
+
+        Log.i(TAG, "Test result stats file: " + mOutputFile);
+
+        // Print the header.
+        mOutputWriter.println(HEADER);
+        mOutputWriter.flush();
+    }
+
+    private String guessTestModuleName() {
+        // Assume the current directory name is the test module name.
+        File cwd;
+        try {
+            cwd = new File(".").getCanonicalFile();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to get the current directory", e);
+        }
+        return cwd.getName();
+    }
+
+    private void addResult(Description classDescription, Description methodDescription,
+            Result result) {
+        mStats.compute(classDescription, (classDesc, value) -> {
+            if (value == null) {
+                value = new HashMap<>();
+            }
+            value.put(methodDescription, result);
+            return value;
+        });
+    }
+
+    public void onClassSkipped(Description classDescription) {
+        addResult(classDescription, Description.EMPTY, Result.Skipped);
+        onClassFinished(classDescription);
+    }
+
+    public void onTestFinished(Description classDescription, Description testDescription,
+            Result result) {
+        addResult(classDescription, testDescription, result);
+    }
+
+    public void onClassFinished(Description classDescription) {
+        int passed = 0;
+        int skipped = 0;
+        int failed = 0;
+        for (var e : mStats.get(classDescription).values()) {
+            switch (e) {
+                case Passed: passed++; break;
+                case Skipped: skipped++; break;
+                case Failed: failed++; break;
+            }
+        }
+
+        var testClass = extractTestClass(classDescription);
+
+        mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+                mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
+                classDescription, passed, failed, skipped);
+        mOutputWriter.flush();
+    }
+
+    /**
+     * Try to extract the class from a description, which is needed because
+     * ParameterizedAndroidJunit4's description doesn't contain a class.
+     */
+    private Class<?> extractTestClass(Description desc) {
+        if (desc.getTestClass() != null) {
+            return desc.getTestClass();
+        }
+        // Look into the children.
+        for (var child : desc.getChildren()) {
+            var fromChild = extractTestClass(child);
+            if (fromChild != null) {
+                return fromChild;
+            }
+        }
+        return null;
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..2b55ac5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,359 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.common.SneakyThrow;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A test runner used for Ravenwood.
+ *
+ * TODO: Handle ENABLE_PROBE_IGNORED
+ *
+ * It will delegate to another runner specified with {@link InnerRunner}
+ * (default = {@link BlockJUnit4ClassRunner}) with the following features.
+ * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
+ *   the inner runner gets a chance to run. This can be used to initialize stuff used by the
+ *   inner runner.
+ * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
+ *   the four test rules such as {@link #sImplicitClassMinRule}, which are also injected by
+ *   the ravenizer tool.
+ *
+ * We use this runner to:
+ * - Initialize the bare minimum environmnet just to be enough to make the actual test runners
+ *   happy.
+ * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
+ *
+ * This class is built such that it can also be used on a real device, but in that case
+ * it will basically just delegate to the inner wrapper, and won't do anything special.
+ * (no hooks, etc.)
+ */
+public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
+    private static final String TAG = "RavenwoodAwareTestRunner";
+
+    @Inherited
+    @Target({TYPE})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface InnerRunner {
+        Class<? extends Runner> value();
+    }
+
+    /**
+     * An annotation similar to JUnit's BeforeClass, but this gets executed before
+     * the inner runner is instantiated, and only on Ravenwood.
+     * It can be used to initialize what's needed by the inner runner.
+     */
+    @Target({METHOD})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface RavenwoodTestRunnerInitializing {
+    }
+
+    /** Scope of a hook. */
+    public enum Scope {
+        Runner,
+        Class,
+        Instance,
+    }
+
+    /** Order of a hook. */
+    public enum Order {
+        First,
+        Last,
+    }
+
+    // The following four rule instances will be injected to tests by the Ravenizer tool.
+
+    public static final TestRule sImplicitClassMinRule = (base, description) ->
+            getCurrentRunner().updateStatement(base, description, Scope.Class, Order.First);
+
+    public static final TestRule sImplicitClassMaxRule = (base, description) ->
+            getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Last);
+
+    public static final TestRule sImplicitInstMinRule = (base, description) ->
+            getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.First);
+
+    public static final TestRule sImplicitInstMaxRule = (base, description) ->
+            getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.Last);
+
+    public static final String IMPLICIT_CLASS_MIN_RULE_NAME = "sImplicitClassMinRule";
+    public static final String IMPLICIT_CLASS_MAX_RULE_NAME = "sImplicitClassMaxRule";
+    public static final String IMPLICIT_INST_MIN_RULE_NAME = "sImplicitInstMinRule";
+    public static final String IMPLICIT_INST_MAX_RULE_NAME = "sImplicitInstMaxRule";
+
+    /** Keeps track of the runner on the current thread. */
+    private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
+
+    private static RavenwoodAwareTestRunner getCurrentRunner() {
+        var runner = sCurrentRunner.get();
+        if (runner == null) {
+            throw new RuntimeException("Current test runner not set!");
+        }
+        return runner;
+    }
+
+    private final TestClass mTestClsas;
+    private final Runner mRealRunner;
+
+    /** Simple logging method. */
+    private void log(String message) {
+        RavenwoodCommonUtils.log(TAG, "[" + getTestClass() + "  @" + this + "] " + message);
+    }
+
+    private Error logAndFail(String message, Throwable innerException) {
+        log(message);
+        log("    Exception=" + innerException);
+        throw new AssertionError(message, innerException);
+    }
+
+    public TestClass getTestClass() {
+        return mTestClsas;
+    }
+
+    /**
+     * Constructor.
+     */
+    public RavenwoodAwareTestRunner(Class<?> testClass) {
+        mTestClsas = new TestClass(testClass);
+
+        /*
+         * If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner,
+         * which simply skips it.
+         */
+        if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+                mTestClsas.getJavaClass())) {
+            mRealRunner = new ClassSkippingTestRunner(mTestClsas);
+            return;
+        }
+
+        // Find the real runner.
+        final Class<? extends Runner> realRunner;
+        final InnerRunner innerRunnerAnnotation = mTestClsas.getAnnotation(InnerRunner.class);
+        if (innerRunnerAnnotation != null) {
+            realRunner = innerRunnerAnnotation.value();
+        } else {
+            // Default runner.
+            realRunner = BlockJUnit4ClassRunner.class;
+        }
+
+        onRunnerInitializing();
+
+        try {
+            log("Initializing the inner runner: " + realRunner);
+
+            mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass);
+
+        } catch (InstantiationException | IllegalAccessException
+                 | InvocationTargetException | NoSuchMethodException e) {
+            throw logAndFail("Failed to instantiate " + realRunner, e);
+        }
+    }
+
+    /**
+     * Run the bare minimum setup to initialize the wrapped runner.
+     */
+    // This method is called by the ctor, so never make it virtual.
+    private void onRunnerInitializing() {
+        if (!isOnRavenwood()) {
+            return;
+        }
+
+        log("onRunnerInitializing");
+
+        RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas);
+
+        // Hook point to allow more customization.
+        runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
+    }
+
+    private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
+            Object instance) {
+        if (!isOnRavenwood()) {
+            return;
+        }
+        log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+
+        for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
+            ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
+
+            var methodDesc = method.getDeclaringClass().getName() + "."
+                    + method.getMethod().toString();
+            try {
+                method.getMethod().invoke(instance);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw logAndFail("Caught exception while running method " + methodDesc, e);
+            }
+        }
+    }
+
+    @Override
+    public Description getDescription() {
+        return mRealRunner.getDescription();
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        if (mRealRunner instanceof ClassSkippingTestRunner) {
+            mRealRunner.run(notifier);
+            RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
+            return;
+        }
+
+        sCurrentRunner.set(this);
+        try {
+            runWithHooks(getDescription(), Scope.Runner, Order.First,
+                    () -> mRealRunner.run(notifier));
+        } finally {
+            sCurrentRunner.remove();
+        }
+    }
+
+    @Override
+    public void filter(Filter filter) throws NoTestsRemainException {
+        if (mRealRunner instanceof Filterable r) {
+            r.filter(filter);
+        }
+    }
+
+    @Override
+    public void order(Orderer orderer) throws InvalidOrderingException {
+        if (mRealRunner instanceof Orderable r) {
+            r.order(orderer);
+        }
+    }
+
+    @Override
+    public void sort(Sorter sorter) {
+        if (mRealRunner instanceof Sortable r) {
+            r.sort(sorter);
+        }
+    }
+
+    private Statement updateStatement(Statement base, Description description, Scope scope,
+            Order order) {
+        if (!isOnRavenwood()) {
+            return base;
+        }
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                runWithHooks(description, scope, order, base);
+            }
+        };
+    }
+
+    private void runWithHooks(Description description, Scope scope, Order order, Runnable r) {
+        runWithHooks(description, scope, order, new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                r.run();
+            }
+        });
+    }
+
+    private void runWithHooks(Description description, Scope scope, Order order, Statement s) {
+        if (isOnRavenwood()) {
+            Assume.assumeTrue(
+                    RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
+        }
+        try {
+            s.evaluate();
+            if (isOnRavenwood()) {
+                RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, null);
+            }
+        } catch (Throwable t) {
+            boolean shouldThrow = true;
+            if (isOnRavenwood()) {
+                shouldThrow = RavenwoodAwareTestRunnerHook.onAfter(
+                        this, description, scope, order, t);
+            }
+            if (shouldThrow) {
+                SneakyThrow.sneakyThrow(t);
+            }
+        }
+    }
+
+    /**
+     * A runner that simply skips a class. It still has to support {@link Filterable}
+     * because otherwise the result still says "SKIPPED" even when it's not included in the
+     * filter.
+     */
+    private static class ClassSkippingTestRunner extends Runner implements Filterable {
+        private final TestClass mTestClass;
+        private final Description mDescription;
+        private boolean mFilteredOut;
+
+        ClassSkippingTestRunner(TestClass testClass) {
+            mTestClass = testClass;
+            mDescription = Description.createTestDescription(
+                    testClass.getJavaClass(), testClass.getJavaClass().getSimpleName());
+            mFilteredOut = false;
+        }
+
+        @Override
+        public Description getDescription() {
+            return mDescription;
+        }
+
+        @Override
+        public void run(RunNotifier notifier) {
+            if (mFilteredOut) {
+                return;
+            }
+            notifier.fireTestSuiteStarted(mDescription);
+            notifier.fireTestIgnored(mDescription);
+            notifier.fireTestSuiteFinished(mDescription);
+        }
+
+        @Override
+        public void filter(Filter filter) throws NoTestsRemainException {
+            if (filter.shouldRun(mDescription)) {
+                mFilteredOut = false;
+            } else {
+                throw new NoTestsRemainException();
+            }
+        }
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 6c8d96a..85297fe 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -16,37 +16,20 @@
 
 package android.platform.test.ravenwood;
 
-import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
-import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
-import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
-import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode;
-
-import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.annotations.EnabledOnRavenwood;
-
-import org.junit.Assume;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 /**
- * {@code @ClassRule} that respects Ravenwood-specific class annotations. This rule has no effect
- * when tests are run on non-Ravenwood test environments.
+ * No longer needed.
  *
- * By default, all tests are executed on Ravenwood, but annotations such as
- * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
- * and class level to "ignore" tests that may not be ready.
+ * @deprecated this class used to be used to handle the class level annotation, which
+ * is now done by the test runner, so this class is not needed.
  */
+@Deprecated
 public class RavenwoodClassRule implements TestRule {
     @Override
     public Statement apply(Statement base, Description description) {
-        if (!IS_ON_RAVENWOOD) {
-            // No check on a real device.
-        } else if (ENABLE_PROBE_IGNORED) {
-            Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
-        } else {
-            Assume.assumeTrue(shouldEnableOnRavenwood(description));
-        }
         return base;
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 74de444..d569896 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -20,22 +20,20 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.SYSTEM;
 
-import static org.junit.Assert.fail;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.log;
 
+import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.EnabledOnRavenwood;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 
-import org.junit.Assume;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -56,16 +54,18 @@
  * before a test class is fully initialized.
  */
 public class RavenwoodRule implements TestRule {
+    private static final String TAG = "RavenwoodRule";
+
     static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
 
     /**
-     * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
-     * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
+     * When this flag is enabled, all tests will be unconditionally run on Ravenwood to detect
+     * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}.
      *
      * This is typically helpful for internal maintainers discovering tests that had previously
      * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
      */
-    static final boolean ENABLE_PROBE_IGNORED = "1".equals(
+    private static final boolean RUN_DISABLED_TESTS = "1".equals(
             System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
 
     /**
@@ -90,23 +90,17 @@
      *
      * Because we use a regex-find, setting "." would disable all tests.
      */
-    private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile(
-            Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), ""));
+    private static final Pattern REALLY_DISABLED_PATTERN = Pattern.compile(
+            Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLED"), ""));
 
-    private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
-            !REALLY_DISABLE_PATTERN.pattern().isEmpty();
-
-    /**
-     * If true, enable optional validation on running tests.
-     */
-    private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals(
-            System.getenv("RAVENWOOD_OPTIONAL_VALIDATION"));
+    private static final boolean HAS_REALLY_DISABLE_PATTERN =
+            !REALLY_DISABLED_PATTERN.pattern().isEmpty();
 
     static {
-        if (ENABLE_PROBE_IGNORED) {
-            System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
-            if (ENABLE_REALLY_DISABLE_PATTERN) {
-                System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern());
+        if (RUN_DISABLED_TESTS) {
+            log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
+            if (HAS_REALLY_DISABLE_PATTERN) {
+                log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern());
             }
         }
     }
@@ -273,101 +267,18 @@
                 "Instrumentation is only available during @Test execution");
     }
 
-    /**
-     * Determine if the given {@link Description} should be enabled when running on the
-     * Ravenwood test environment.
-     *
-     * A more specific method-level annotation always takes precedence over any class-level
-     * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
-     * an {@link DisabledOnRavenwood} annotation.
-     */
-    static boolean shouldEnableOnRavenwood(Description description) {
-        // First, consult any method-level annotations
-        if (description.isTest()) {
-            // Stopgap for http://g/ravenwood/EPAD-N5ntxM
-            if (description.getMethodName().endsWith("$noRavenwood")) {
-                return false;
-            }
-            if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
-                return true;
-            }
-            if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
-                return false;
-            }
-            if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                return false;
-            }
-        }
-
-        // Otherwise, consult any class-level annotations
-        final var clazz = description.getTestClass();
-        if (clazz != null) {
-            if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
-                return true;
-            }
-            if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
-                return false;
-            }
-            if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                return false;
-            }
-        }
-
-        // When no annotations have been requested, assume test should be included
-        return true;
-    }
-
-    static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) {
-        if (!ENABLE_REALLY_DISABLE_PATTERN) {
-            return false;
-        }
-
-        final var fullname = description.getTestClass().getName()
-                + (description.isTest() ? "#" + description.getMethodName() : "");
-
-        if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) {
-            System.out.println("Still ignoring " + fullname);
-            return true;
-        }
-        return false;
-    }
 
     @Override
     public Statement apply(Statement base, Description description) {
-        // No special treatment when running outside Ravenwood; run tests as-is
-        if (!IS_ON_RAVENWOOD) {
-            return base;
-        }
-
-        if (ENABLE_PROBE_IGNORED) {
-            return applyProbeIgnored(base, description);
-        } else {
-            return applyDefault(base, description);
-        }
-    }
-
-    private void commonPrologue(Statement base, Description description) throws IOException {
-        RavenwoodRuleImpl.logTestRunner("started", description);
-        RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION);
-        RavenwoodRuleImpl.init(RavenwoodRule.this);
-    }
-
-    /**
-     * Run the given {@link Statement} with no special treatment.
-     */
-    private Statement applyDefault(Statement base, Description description) {
+        // TODO: Here, we're calling init() / reset() once for each rule.
+        // That means if a test class has multiple rules -- even if they refer to the same
+        // rule instance -- we're calling them multiple times. We need to fix it.
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                Assume.assumeTrue(shouldEnableOnRavenwood(description));
-
-                commonPrologue(base, description);
+                RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
                     base.evaluate();
-                    RavenwoodRuleImpl.logTestRunner("finished", description);
-                } catch (Throwable t) {
-                    RavenwoodRuleImpl.logTestRunner("failed", description);
-                    throw t;
                 } finally {
                     RavenwoodRuleImpl.reset(RavenwoodRule.this);
                 }
@@ -376,44 +287,6 @@
     }
 
     /**
-     * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
-     * run on Ravenwood to detect cases where a test is able to pass despite being marked as
-     * {@code IgnoreUnderRavenwood}.
-     */
-    private Statement applyProbeIgnored(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
-
-                commonPrologue(base, description);
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    // If the test isn't included, eat the exception and report the
-                    // assumption failure that test authors expect; otherwise throw
-                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
-                    throw t;
-                } finally {
-                    RavenwoodRuleImpl.logTestRunner("finished", description);
-                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
-                }
-
-                if (!shouldEnableOnRavenwood(description)) {
-                    fail("Test wasn't included under Ravenwood, but it actually "
-                            + "passed under Ravenwood; consider updating annotations");
-                }
-            }
-        };
-    }
-
-    public static class _$RavenwoodPrivate {
-        public static boolean isOptionalValidationEnabled() {
-            return ENABLE_OPTIONAL_VALIDATION;
-        }
-    }
-
-    /**
      * Returns the "real" result from {@link System#currentTimeMillis()}.
      *
      * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
@@ -423,4 +296,47 @@
     public long realCurrentTimeMillis() {
         return System.currentTimeMillis();
     }
+
+    // Below are internal to ravenwood. Don't use them from normal tests...
+
+    public static class RavenwoodPrivate {
+        private RavenwoodPrivate() {
+        }
+
+        private volatile Boolean mRunDisabledTestsOverride = null;
+
+        private volatile Pattern mReallyDisabledPattern = null;
+
+        public boolean isRunningDisabledTests() {
+            if (mRunDisabledTestsOverride != null) {
+                return mRunDisabledTestsOverride;
+            }
+            return RUN_DISABLED_TESTS;
+        }
+
+        public Pattern getReallyDisabledPattern() {
+            if (mReallyDisabledPattern != null) {
+                return mReallyDisabledPattern;
+            }
+            return REALLY_DISABLED_PATTERN;
+        }
+
+        public void overrideRunDisabledTest(boolean runDisabledTests,
+                @Nullable String reallyDisabledPattern) {
+            mRunDisabledTestsOverride = runDisabledTests;
+            mReallyDisabledPattern =
+                    reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern);
+        }
+
+        public void resetRunDisabledTest() {
+            mRunDisabledTestsOverride = null;
+            mReallyDisabledPattern = null;
+        }
+    }
+
+    private static final RavenwoodPrivate sRavenwoodPrivate = new  RavenwoodPrivate();
+
+    public static RavenwoodPrivate private$ravenwood() {
+        return sRavenwoodPrivate;
+    }
 }
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
new file mode 100644
index 0000000..1e4889c
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.platform.test.ravenwood;
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Provide hook points created by {@link RavenwoodAwareTestRunner}. This is a version
+ * that's used on a device side test.
+ *
+ * All methods are no-op in real device tests.
+ *
+ * TODO: Use some kind of factory to provide different implementation for the device test
+ * and the ravenwood test.
+ */
+public class RavenwoodAwareTestRunnerHook {
+    private RavenwoodAwareTestRunnerHook() {
+    }
+
+    /**
+     * Called when a runner starts, before the inner runner gets a chance to run.
+     */
+    public static void onRunnerInitializing(Runner runner, TestClass testClass) {
+    }
+
+    /**
+     * Called when a whole test class is skipped.
+     */
+    public static void onClassSkipped(Description description) {
+    }
+
+    /**
+     * Called before a test / class.
+     *
+     * Return false if it should be skipped.
+     */
+    public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
+            Scope scope, Order order) {
+        return true;
+    }
+
+    /**
+     * Called after a test / class.
+     *
+     * Return false if the exception should be ignored.
+     */
+    public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
+            Scope scope, Order order, Throwable th) {
+        return true;
+    }
+
+    public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
+        return true;
+    }
+}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 483b98a..a470626 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,7 +17,6 @@
 package android.platform.test.ravenwood;
 
 import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 
 public class RavenwoodRuleImpl {
     public static void init(RavenwoodRule rule) {
@@ -32,10 +31,6 @@
         // No-op when running on a real device
     }
 
-    public static void validate(Statement base, Description description,
-            boolean enableOptionalValidation) {
-    }
-
     public static long realCurrentTimeMillis() {
         return System.currentTimeMillis();
     }
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 9a11a8a..7b5bc5a 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -21,6 +21,8 @@
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.Arrays;
 
 public class RavenwoodCommonUtils {
@@ -51,6 +53,8 @@
     public static final String RAVENWOOD_EMPTY_RESOURCES_APK =
             RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
 
+    public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
+
     // @GuardedBy("sLock")
     private static boolean sIntegrityChecked = false;
 
@@ -77,6 +81,18 @@
         return sEnableExtraRuntimeCheck;
     }
 
+    /** Simple logging method. */
+    public static void log(String tag, String message) {
+        // Avoid using Android's Log class, which could be broken for various reasons.
+        // (e.g. the JNI file doesn't exist for whatever reason)
+        System.out.print(tag + ": " + message + "\n");
+    }
+
+    /** Simple logging method. */
+    private void log(String tag, String format, Object... args) {
+        log(tag, String.format(format, args));
+    }
+
     /**
      * Load the main runtime JNI library.
      */
@@ -236,4 +252,17 @@
         var is = new FileInputStream(fd);
         RavenwoodCommonUtils.closeQuietly(is);
     }
+
+    public static void ensureIsPublicVoidMethod(Method method, boolean isStatic) {
+        var ok = Modifier.isPublic(method.getModifiers())
+                && (Modifier.isStatic(method.getModifiers()) == isStatic)
+                && (method.getReturnType() == void.class);
+        if (ok) {
+            return; // okay
+        }
+        throw new AssertionError(String.format(
+                "Method %s.%s() expected to be public %svoid",
+                method.getDeclaringClass().getName(), method.getName(),
+                (isStatic ? "static " : "")));
+    }
 }
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
index 706a055..f894b0e 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java
@@ -37,6 +37,9 @@
      * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
      */
     public static void ensureRavenwoodInitialized() {
+
+        // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook.
+
         synchronized (sInitializeLock) {
             if (sInitialized) {
                 return;
diff --git a/ravenwood/scripts/list-ravenwood-tests.sh b/ravenwood/scripts/list-ravenwood-tests.sh
index fb9b823..05f3fdf 100755
--- a/ravenwood/scripts/list-ravenwood-tests.sh
+++ b/ravenwood/scripts/list-ravenwood-tests.sh
@@ -15,4 +15,4 @@
 
 # List all the ravenwood test modules.
 
-jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json"
+jq -r 'to_entries[] | select( .value.compatibility_suites | index("ravenwood-tests") ) | .key' "$OUT/module-info.json" | sort
diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh
new file mode 100755
index 0000000..b6cf5b8
--- /dev/null
+++ b/ravenwood/scripts/update-test-mapping.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+# 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.
+
+# Update f/b/r/TEST_MAPPING with all the ravenwood tests as presubmit.
+#
+# Note, before running it, make sure module-info.json is up-to-date by running
+# (any) build.
+
+set -e
+
+main() {
+    local script_name="${0##*/}"
+    local script_dir="${0%/*}"
+    local test_mapping="$script_dir/../TEST_MAPPING"
+    local test_mapping_bak="$script_dir/../TEST_MAPPING.bak"
+
+    local header="$(sed -ne '1,/AUTO-GENERATED-START/p' "$test_mapping")"
+    local footer="$(sed -ne '/AUTO-GENERATED-END/,$p' "$test_mapping")"
+
+    echo "Getting all tests"
+    local tests=( $("$script_dir/list-ravenwood-tests.sh") )
+
+    local num_tests="${#tests[@]}"
+
+    if (( $num_tests == 0 )) ; then
+        echo "Something went wrong. No ravenwood tests detected." 1>&2
+        return 1
+    fi
+
+    echo "Tests: ${tests[@]}"
+
+    echo "Creating backup at $test_mapping_bak"
+    cp "$test_mapping" "$test_mapping_bak"
+
+    echo "Updating $test_mapping"
+    {
+        echo "$header"
+
+        echo "    // DO NOT MODIFY MANUALLY"
+        echo "    // Use scripts/$script_name to update it."
+
+        local i=0
+        while (( $i < $num_tests )) ; do
+            local comma=","
+            if (( $i == ($num_tests - 1) )); then
+                comma=""
+            fi
+            echo "    {"
+            echo "      \"name\": \"${tests[$i]}\","
+            echo "      \"host\": true"
+            echo "    }$comma"
+
+            i=$(( $i + 1 ))
+        done
+
+        echo "$footer"
+    } >"$test_mapping"
+
+    if cmp "$test_mapping_bak" "$test_mapping" ; then
+        echo "No change detecetd."
+        return 0
+    fi
+    echo "Updated $test_mapping"
+
+    # `|| true` is needed because of `set -e`.
+    diff -u "$test_mapping_bak" "$test_mapping" || true
+    return 0
+}
+
+main
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 5cffdec..d8366c5 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@
 android.util.SparseIntArray
 android.util.SparseLongArray
 android.util.SparseSetArray
+android.util.StateSet
 android.util.StringBuilderPrinter
 android.util.TeeWriter
 android.util.TimeUtils
@@ -222,9 +223,11 @@
 android.content.res.AssetFileDescriptor
 android.content.res.AssetManager
 android.content.res.AssetManager$Builder
+android.content.res.ColorStateList
 android.content.res.ConfigurationBoundResourceCache
 android.content.res.Configuration
 android.content.res.CompatibilityInfo
+android.content.res.ComplexColor
 android.content.res.ConstantState
 android.content.res.DrawableCache
 android.content.res.Element
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
similarity index 70%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
index d60f14c..3a7fab3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:Suppress("ktlint:standard:filename")
 
-package com.android.systemui.volume
+package com.android.platform.test.ravenwood.ravenizer
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+/**
+ * Use it for internal exception that really shouldn't happen.
+ */
+class RavenizerInternalException(message: String) : Exception(message)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index da9c7d9..e92ef72 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -20,7 +20,7 @@
 import com.android.hoststubgen.asm.zipEntryNameToClassName
 import com.android.hoststubgen.executableName
 import com.android.hoststubgen.log
-import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.ClassVisitor
 import org.objectweb.asm.ClassWriter
@@ -177,7 +177,8 @@
      * Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
      */
     private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
-        return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+        return !classInternalName.shouldByBypassed()
+                && RunnerRewritingAdapter.shouldProcess(classes, classInternalName)
     }
 
     private fun processSingleClass(
@@ -191,6 +192,9 @@
 
         lateinit var data: ByteArray
         stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+
+            val classInternalName = zipEntryNameToClassName(entry.name)
+                ?: throw RavenizerInternalException("Unexpected zip entry name: ${entry.name}")
             val flags = ClassWriter.COMPUTE_MAXS
             val cw = ClassWriter(flags)
             var outVisitor: ClassVisitor = cw
@@ -201,7 +205,8 @@
             }
 
             // This must be kept in sync with shouldProcessClass.
-            outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+            outVisitor = RunnerRewritingAdapter.maybeApply(
+                classInternalName, allClasses, outVisitor)
 
             cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
 
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 0018648..e026e7a 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,18 +15,31 @@
  */
 package com.android.platform.test.ravenwood.ravenizer
 
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.startsWithAny
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
 import org.objectweb.asm.Type
 
-val junitTestMethodType = Type.getType(org.junit.Test::class.java)
-val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+data class TypeHolder(
+    val clazz: Class<*>,
+) {
+    val type = Type.getType(clazz)
+    val desc = type.descriptor
+    val descAsSet = setOf<String>(desc)
+    val internlName = type.internalName
+}
 
-val junitTestMethodDescriptor = junitTestMethodType.descriptor
-val junitRunWithDescriptor = junitRunWithType.descriptor
+val testAnotType = TypeHolder(org.junit.Test::class.java)
+val ruleAnotType = TypeHolder(org.junit.Rule::class.java)
+val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
+val runWithAnotType = TypeHolder(RunWith::class.java)
+val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
 
-val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
-val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+val testRuleType = TypeHolder(TestRule::class.java)
+val ravenwoodTestRunnerType = TypeHolder(RavenwoodAwareTestRunner::class.java)
 
 /**
  * Returns true, if a test looks like it's a test class which needs to be processed.
@@ -39,16 +52,44 @@
 
     val cn = classes.findClass(className) ?: return false
 
-    if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+    if (cn.findAnyAnnotation(runWithAnotType.descAsSet) != null) {
         return true
     }
     cn.methods?.forEach { method ->
-        if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+        if (method.findAnyAnnotation(testAnotType.descAsSet) != null) {
             return true
         }
     }
+
+    // Check the super class.
     if (cn.superName == null) {
         return false
     }
     return isTestLookingClass(classes, cn.superName)
 }
+
+fun String.isRavenwoodClass(): Boolean {
+    return this.startsWithAny(
+        "com/android/hoststubgen/",
+        "android/platform/test/ravenwood",
+        "com/android/ravenwood/",
+        "com/android/platform/test/ravenwood/",
+    )
+}
+
+/**
+ * Classes that should never be modified.
+ */
+fun String.shouldByBypassed(): Boolean {
+    if (this.isRavenwoodClass()) {
+        return true
+    }
+    return this.startsWithAny(
+        "java/", // just in case...
+        "javax/",
+        "org/junit/",
+        "org/mockito/",
+        "kotlin/",
+        // TODO -- anything else?
+    )
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
new file mode 100644
index 0000000..25cad02
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -0,0 +1,453 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer.adapter
+
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner
+import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnnotationValueAsType
+import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException
+import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
+import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
+import com.android.platform.test.ravenwood.ravenizer.runWithAnotType
+import com.android.platform.test.ravenwood.ravenizer.testRuleType
+import org.objectweb.asm.AnnotationVisitor
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Opcodes.ACC_FINAL
+import org.objectweb.asm.Opcodes.ACC_PUBLIC
+import org.objectweb.asm.Opcodes.ACC_STATIC
+import org.objectweb.asm.commons.AdviceAdapter
+import org.objectweb.asm.tree.ClassNode
+
+/**
+ * Class visitor to update the RunWith and inject some necessary rules.
+ *
+ * - Change the @RunWith(RavenwoodAwareTestRunner.class).
+ * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...).
+ * - Add RavenwoodAwareTestRunner's member rules as junit rules.
+ * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX.
+ */
+class RunnerRewritingAdapter private constructor(
+    protected val classes: ClassNodes,
+    nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+    /** Arbitrary cut-off point when deciding whether to change the order or an existing rule.*/
+    val RULE_ORDER_TWEAK_CUTOFF = 1973020500
+
+    /** Current class's internal name */
+    lateinit var classInternalName: String
+
+    /** [ClassNode] for the current class */
+    lateinit var classNode: ClassNode
+
+    /** True if this visitor is generating code. */
+    var isGeneratingCode = false
+
+    /** Run a [block] with [isGeneratingCode] set to true. */
+    private inline fun <T> generateCode(block: () -> T): T {
+        isGeneratingCode = true
+        try {
+            return block()
+        } finally {
+            isGeneratingCode = false
+        }
+    }
+
+    override fun visit(
+        version: Int,
+        access: Int,
+        name: String?,
+        signature: String?,
+        superName: String?,
+        interfaces: Array<out String>?,
+        ) {
+        classInternalName = name!!
+        classNode = classes.getClass(name)
+        if (!isTestLookingClass(classes, name)) {
+            throw RavenizerInternalException("This adapter shouldn't be used for non-test class")
+        }
+        super.visit(version, access, name, signature, superName, interfaces)
+
+        generateCode {
+            injectRunWithAnnotation()
+            if (!classes.hasClassInitializer(classInternalName)) {
+                injectStaticInitializer()
+            }
+            injectRules()
+        }
+    }
+
+    /**
+     * Remove the original @RunWith annotation.
+     */
+    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
+        if (!isGeneratingCode && runWithAnotType.desc == descriptor) {
+            return null
+        }
+        return super.visitAnnotation(descriptor, visible)
+    }
+
+    override fun visitField(
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        value: Any?
+    ): FieldVisitor {
+        val fallback = super.visitField(access, name, descriptor, signature, value)
+        if (isGeneratingCode) {
+            return fallback
+        }
+        return FieldRuleOrderRewriter(name, fallback)
+    }
+
+    /** Inject an empty <clinit>. The body will be injected by [visitMethod]. */
+    private fun injectStaticInitializer() {
+        visitMethod(
+            Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
+            CLASS_INITIALIZER_NAME,
+            CLASS_INITIALIZER_DESC,
+            null,
+            null
+        )!!.let { mv ->
+            mv.visitCode()
+            mv.visitInsn(Opcodes.RETURN)
+            mv.visitMaxs(0, 0)
+            mv.visitEnd()
+        }
+    }
+
+    /**
+     * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has
+     * a `@RunWith`, then change it to add a `@OrigRunWith`.
+     */
+    private fun injectRunWithAnnotation() {
+        // Extract the original RunWith annotation and its value.
+        val runWith = classNode.findAnyAnnotation(runWithAnotType.descAsSet)
+        val runWithClass = runWith?.let { an ->
+            findAnnotationValueAsType(an, "value")
+        }
+
+        if (runWith != null) {
+            if (runWithClass == ravenwoodTestRunnerType.type) {
+                // It already uses RavenwoodTestRunner. We'll just keep it, but we need to
+                // inject it again because the original one is removed by visitAnnotation().
+                log.d("Class ${classInternalName.toHumanReadableClassName()}" +
+                        " already uses RavenwoodTestRunner.")
+                visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+                    av.visit("value", ravenwoodTestRunnerType)
+                    av.visitEnd()
+                }
+                return
+            }
+            if (runWithClass == null) {
+                throw ClassParseException("@RunWith annotation doesn't have a property \"value\""
+                        + " in class ${classInternalName.toHumanReadableClassName()}")
+            }
+
+            // Inject an @OrigRunWith.
+            visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av ->
+                av.visit("value", runWithClass)
+                av.visitEnd()
+            }
+        }
+
+        // Inject a @RunWith(RavenwoodAwareTestRunner.class).
+        visitAnnotation(runWithAnotType.desc, true)!!.let { av ->
+            av.visit("value", ravenwoodTestRunnerType.type)
+            av.visitEnd()
+        }
+        log.d("Processed ${classInternalName.toHumanReadableClassName()}")
+    }
+
+    /*
+     Generate the fields and the ctor, which should looks like  this:
+
+  public static final org.junit.rules.TestRule sRavenwoodImplicitClassMinRule;
+    descriptor: Lorg/junit/rules/TestRule;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      0: #49(#50=I#51)
+        org.junit.ClassRule(
+          order=-2147483648
+        )
+
+  public static final org.junit.rules.TestRule sRavenwoodImplicitClassMaxRule;
+    descriptor: Lorg/junit/rules/TestRule;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      0: #49(#50=I#52)
+        org.junit.ClassRule(
+          order=2147483647
+        )
+
+  public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMinRule;
+    descriptor: Lorg/junit/rules/TestRule;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      0: #53(#50=I#51)
+        org.junit.Rule(
+          order=-2147483648
+        )
+
+  public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMaxRule;
+    descriptor: Lorg/junit/rules/TestRule;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      0: #53(#50=I#52)
+        org.junit.Rule(
+          order=2147483647
+        )
+     */
+
+    val sRavenwood_ClassRuleMin = "sRavenwood_ClassRuleMin"
+    val sRavenwood_ClassRuleMax = "sRavenwood_ClassRuleMax"
+    val mRavenwood_InstRuleMin = "mRavenwood_InstRuleMin"
+    val mRavenwood_InstRuleMax = "mRavenwood_InstRuleMax"
+
+    private fun injectRules() {
+        injectRule(sRavenwood_ClassRuleMin, true, Integer.MIN_VALUE)
+        injectRule(sRavenwood_ClassRuleMax, true, Integer.MAX_VALUE)
+        injectRule(mRavenwood_InstRuleMin, false, Integer.MIN_VALUE)
+        injectRule(mRavenwood_InstRuleMax, false, Integer.MAX_VALUE)
+    }
+
+    private fun injectRule(fieldName: String, isStatic: Boolean, order: Int) {
+        visitField(
+            ACC_PUBLIC or ACC_FINAL or (if (isStatic) ACC_STATIC else 0),
+            fieldName,
+            testRuleType.desc,
+            null,
+            null,
+        ).let { fv ->
+            val anot = if (isStatic) { classRuleAnotType } else { ruleAnotType }
+            fv.visitAnnotation(anot.desc, true).let {
+                it.visit("order", order)
+                it.visitEnd()
+            }
+            fv.visitEnd()
+        }
+    }
+
+    override fun visitMethod(
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+    ): MethodVisitor {
+        val next = super.visitMethod(access, name, descriptor, signature, exceptions)
+        if (name == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
+            return ClassInitializerVisitor(
+                access, name, descriptor, signature, exceptions, next)
+        }
+        if (name == CTOR_NAME) {
+            return ConstructorVisitor(
+                access, name, descriptor, signature, exceptions, next)
+        }
+        return next
+    }
+
+    /*
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: getstatic     #36                 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+         3: putstatic     #39                 // Field sRavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule;
+         6: getstatic     #42                 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+         9: putstatic     #45                 // Field sRavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule;
+        12: return
+      LineNumberTable:
+        line 33: 0
+        line 36: 6
+     */
+    private inner class ClassInitializerVisitor(
+        access: Int,
+        val name: String,
+        val descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        next: MethodVisitor?,
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitCode() {
+            visitFieldInsn(Opcodes.GETSTATIC,
+                ravenwoodTestRunnerType.internlName,
+                RavenwoodAwareTestRunner.IMPLICIT_CLASS_MIN_RULE_NAME,
+                testRuleType.desc
+            )
+            visitFieldInsn(Opcodes.PUTSTATIC,
+                classInternalName,
+                sRavenwood_ClassRuleMin,
+                testRuleType.desc
+            )
+
+            visitFieldInsn(Opcodes.GETSTATIC,
+                ravenwoodTestRunnerType.internlName,
+                RavenwoodAwareTestRunner.IMPLICIT_CLASS_MAX_RULE_NAME,
+                testRuleType.desc
+            )
+            visitFieldInsn(Opcodes.PUTSTATIC,
+                classInternalName,
+                sRavenwood_ClassRuleMax,
+                testRuleType.desc
+            )
+
+            super.visitCode()
+        }
+    }
+
+    /*
+  public com.android.ravenwoodtest.bivalenttest.runnertest.RavenwoodRunnerTest();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: getstatic     #7                  // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+         8: putfield      #13                 // Field sRavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule;
+        11: aload_0
+        12: getstatic     #18                 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+        15: putfield      #21                 // Field sRavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule;
+        18: return
+      LineNumberTable:
+        line 31: 0
+        line 38: 4
+        line 41: 11
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      19     0  this   Lcom/android/ravenwoodtest/bivalenttest/runnertest/RavenwoodRunnerTest;
+     */
+    private inner class ConstructorVisitor(
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        next: MethodVisitor?,
+    ) : AdviceAdapter(OPCODE_VERSION, next, ACC_ENUM, name, descriptor) {
+        override fun onMethodEnter() {
+            visitVarInsn(ALOAD, 0)
+            visitFieldInsn(Opcodes.GETSTATIC,
+                ravenwoodTestRunnerType.internlName,
+                RavenwoodAwareTestRunner.IMPLICIT_INST_MIN_RULE_NAME,
+                testRuleType.desc
+            )
+            visitFieldInsn(Opcodes.PUTFIELD,
+                classInternalName,
+                mRavenwood_InstRuleMin,
+                testRuleType.desc
+            )
+
+            visitVarInsn(ALOAD, 0)
+            visitFieldInsn(Opcodes.GETSTATIC,
+                ravenwoodTestRunnerType.internlName,
+                RavenwoodAwareTestRunner.IMPLICIT_INST_MAX_RULE_NAME,
+                testRuleType.desc
+            )
+            visitFieldInsn(Opcodes.PUTFIELD,
+                classInternalName,
+                mRavenwood_InstRuleMax,
+                testRuleType.desc
+            )
+        }
+    }
+
+    /**
+     * Rewrite "order" of the existing junit rules to make sure no rules use a MAX or MIN order.
+     *
+     * Currently, we do it a hacky way -- use an arbitrary cut-off point, and if the order
+     * is larger than that, decrement by 1, and if it's smaller than the negative cut-off point,
+     * increment it by 1.
+     *
+     * (or the arbitrary number is already used.... then we're unlucky, let's change the cut-off
+     * point.)
+     */
+    private inner class FieldRuleOrderRewriter(
+        val fieldName: String,
+        next: FieldVisitor,
+    ) : FieldVisitor(OPCODE_VERSION, next) {
+        override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
+            val fallback = super.visitAnnotation(descriptor, visible)
+            if (descriptor != ruleAnotType.desc && descriptor != classRuleAnotType.desc) {
+                return fallback
+            }
+            return RuleOrderRewriter(fallback)
+        }
+
+        private inner class RuleOrderRewriter(
+            next: AnnotationVisitor,
+        ) : AnnotationVisitor(OPCODE_VERSION, next) {
+            override fun visit(name: String?, origValue: Any?) {
+                if (name != "order") {
+                    return super.visit(name, origValue)
+                }
+                var order = origValue as Int
+                if (order == RULE_ORDER_TWEAK_CUTOFF || order == -RULE_ORDER_TWEAK_CUTOFF) {
+                    // Oops. If this happens, we'll need to change RULE_ORDER_TWEAK_CUTOFF.
+                    // Or, we could scan all the rules in the target jar and find an unused number.
+                    // Because rules propagate to subclasses, we'll at least check all the
+                    // super classes of the current class.
+                    throw RavenizerInternalException(
+                        "OOPS: Field $classInternalName.$fieldName uses $order."
+                                + " We can't update it.")
+                }
+                if (order > RULE_ORDER_TWEAK_CUTOFF) {
+                    order -= 1
+                }
+                if (order < -RULE_ORDER_TWEAK_CUTOFF) {
+                    order += 1
+                }
+                super.visit(name, order)
+            }
+        }
+    }
+
+    companion object {
+        fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+            return isTestLookingClass(classes, className)
+        }
+
+        fun maybeApply(
+            className: String,
+            classes: ClassNodes,
+            nextVisitor: ClassVisitor,
+        ): ClassVisitor {
+            if (!shouldProcess(classes, className)) {
+                return nextVisitor
+            } else {
+                return RunnerRewritingAdapter(classes, nextVisitor)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
deleted file mode 100644
index c539908..0000000
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
+++ /dev/null
@@ -1,40 +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.platform.test.ravenwood.ravenizer.adapter
-
-import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.visitors.OPCODE_VERSION
-import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
-import org.objectweb.asm.ClassVisitor
-
-/**
- * Class visitor to rewrite the test runner for Ravenwood
- *
- * TODO: Implement it.
- */
-class TestRunnerRewritingAdapter(
-    protected val classes: ClassNodes,
-    nextVisitor: ClassVisitor,
-) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
-   companion object {
-       /**
-        * Returns true if a target class is interesting to this adapter.
-        */
-       fun shouldProcess(classes: ClassNodes, className: String): Boolean {
-            return isTestLookingClass(classes, className)
-       }
-    }
-}
diff --git a/services/Android.bp b/services/Android.bp
index 0006455..653cd3c3 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -136,6 +136,7 @@
         ":services.searchui-sources",
         ":services.smartspace-sources",
         ":services.soundtrigger-sources",
+        ":services.supervision-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
         ":services.texttospeech-sources",
@@ -237,6 +238,7 @@
         "services.searchui",
         "services.smartspace",
         "services.soundtrigger",
+        "services.supervision",
         "services.systemcaptions",
         "services.translation",
         "services.texttospeech",
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8e2e0ad..08cc9c3 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -121,6 +121,13 @@
 }
 
 flag {
+    name: "enable_magnification_keyboard_control"
+    namespace: "accessibility"
+    description: "Whether to enable keyboard control for magnification"
+    bug: "355487062"
+}
+
+flag {
     name: "fix_drag_pointer_when_ending_drag"
     namespace: "accessibility"
     description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 95559802..7f79556 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -89,7 +89,7 @@
 
     @Override
     protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        cancelPendingTransitions();
         if (!isInsideSlop(rawEvent, mTouchSlop)) {
             cancelGesture(event, rawEvent, policyFlags);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
index 15e1278..872ade5 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -46,7 +46,6 @@
     @Override
     protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         super.onUp(event, rawEvent, policyFlags);
-        cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
     }
 
     @Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java b/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
new file mode 100644
index 0000000..c01fe31
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java
@@ -0,0 +1,130 @@
+/*
+ * 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.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GetSchemaResponse;
+import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.SetSchemaResponse;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class for interacting with a system server local appsearch session synchronously.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public class SyncAppSearchCallHelper implements Closeable {
+    private static final String TAG = SyncAppSearchCallHelper.class.getSimpleName();
+    private final Executor mExecutor;
+    private final AppSearchManager mAppSearchManager;
+    private final AndroidFuture<AppSearchResult<AppSearchSession>> mSettableSessionFuture;
+
+    public SyncAppSearchCallHelper(@NonNull AppSearchManager appSearchManager,
+                                   @NonNull Executor executor,
+                                   @NonNull SearchContext appSearchContext) {
+        Objects.requireNonNull(appSearchManager);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(appSearchContext);
+
+        mExecutor = executor;
+        mAppSearchManager = appSearchManager;
+        mSettableSessionFuture = new AndroidFuture<>();
+        mAppSearchManager.createSearchSession(
+                appSearchContext, mExecutor, mSettableSessionFuture::complete);
+    }
+
+    /**
+     * Converts a failed app search result codes into an exception.
+     */
+    @NonNull
+    private static Exception failedResultToException(@NonNull AppSearchResult appSearchResult) {
+        return switch (appSearchResult.getResultCode()) {
+            case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+                    appSearchResult.getErrorMessage());
+            default -> new IllegalStateException(appSearchResult.getErrorMessage());
+        };
+    }
+
+    private AppSearchSession getSession() throws Exception {
+        AppSearchResult<AppSearchSession> sessionResult = mSettableSessionFuture.get();
+        if (!sessionResult.isSuccess()) {
+            throw failedResultToException(sessionResult);
+        }
+        return sessionResult.getResultValue();
+    }
+
+    /**
+     * Gets the schema for a given app search session.
+     */
+    @WorkerThread
+    public GetSchemaResponse getSchema() throws Exception {
+        AndroidFuture<AppSearchResult<GetSchemaResponse>> settableSchemaResponse =
+                new AndroidFuture<>();
+        getSession().getSchema(mExecutor, settableSchemaResponse::complete);
+        AppSearchResult<GetSchemaResponse> schemaResponse = settableSchemaResponse.get();
+        if (schemaResponse.isSuccess()) {
+            return schemaResponse.getResultValue();
+        } else {
+            throw failedResultToException(schemaResponse);
+        }
+    }
+
+    /**
+     * Sets the schema for a given app search session.
+     */
+    @WorkerThread
+    public SetSchemaResponse setSchema(
+            @NonNull SetSchemaRequest setSchemaRequest) throws Exception {
+        AndroidFuture<AppSearchResult<SetSchemaResponse>> settableSchemaResponse =
+                new AndroidFuture<>();
+        getSession().setSchema(
+                setSchemaRequest, mExecutor, mExecutor, settableSchemaResponse::complete);
+        AppSearchResult<SetSchemaResponse> schemaResponse = settableSchemaResponse.get();
+        if (schemaResponse.isSuccess()) {
+            return schemaResponse.getResultValue();
+        } else {
+            throw failedResultToException(schemaResponse);
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            getSession().close();
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to close app search session", ex);
+        }
+    }
+}
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 2bfdd0a..77650eb 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -28,7 +28,6 @@
     ],
     static_libs: [
         "ukey2_jni",
-        "virtualdevice_flags_lib",
         "virtual_camera_service_aidl-java",
     ],
     lint: {
diff --git a/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
index 4678a16..5fd282d 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
@@ -190,7 +190,7 @@
             }
             mCachedPerUser.set(userId, cachedObservableUuids);
         }
-        return cachedObservableUuids;
+        return cachedObservableUuids == null ? new ArrayList<>() : cachedObservableUuids;
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
deleted file mode 100644
index 66313e6..0000000
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    default_team: "trendy_team_xr_framework",
-}
-
-java_aconfig_library {
-    name: "virtualdevice_flags_lib",
-    aconfig_declarations: "virtualdevice_flags",
-}
-
-aconfig_declarations {
-    name: "virtualdevice_flags",
-    package: "com.android.server.companion.virtual",
-    container: "system",
-    srcs: [
-        "flags.aconfig",
-    ],
-}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
index b0bacfd..fed153f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
@@ -48,9 +48,6 @@
     void logCreated(int deviceId, int ownerUid) {
         final long token = Binder.clearCallingIdentity();
         try {
-            if (!Flags.dumpHistory()) {
-                return;
-            }
             addEntry(new LogEntry(TYPE_CREATED, deviceId, System.currentTimeMillis(), ownerUid));
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -60,9 +57,6 @@
     void logClosed(int deviceId, int ownerUid) {
         final long token = Binder.clearCallingIdentity();
         try {
-            if (!Flags.dumpHistory()) {
-                return;
-            }
             addEntry(new LogEntry(TYPE_CLOSED, deviceId, System.currentTimeMillis(), ownerUid));
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -79,9 +73,6 @@
     void dump(PrintWriter pw) {
         final long token = Binder.clearCallingIdentity();
         try {
-            if (!Flags.dumpHistory()) {
-                return;
-            }
             pw.println("VirtualDevice Log:");
             UidToPackageNameCache packageNameCache = new UidToPackageNameCache(
                     mContext.getPackageManager());
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
deleted file mode 100644
index 616f5d0..0000000
--- a/services/companion/java/com/android/server/companion/virtual/flags.aconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-# OLD PACKAGE, DO NOT USE: Prefer `flags.aconfig` in core/java/android/companion/virtual
-# (or other custom files) to define your flags
-package: "com.android.server.companion.virtual"
-container: "system"
-
-flag {
-  name: "dump_history"
-  namespace: "virtual_devices"
-  description: "This flag controls if a history of virtual devices is shown in dumpsys virtualdevices"
-  bug: "293114719"
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2de4482..1470e9a 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -23,6 +23,7 @@
 import static com.android.server.health.Utils.copyV1Battery;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -67,6 +68,7 @@
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.health.HealthServiceWrapper;
@@ -207,18 +209,18 @@
     private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
             mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
 
-    private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
+    private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both connected/disconnected, so match using key */
-    private Bundle mPowerOptions = BroadcastOptions.makeBasic()
+    private static final Bundle POWER_OPTIONS = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both low/okay, so match using key */
-    private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
+    private static final Bundle BATTERY_OPTIONS = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -226,11 +228,60 @@
 
     private MetricsLogger mMetricsLogger;
 
+    private static final int MSG_BROADCAST_BATTERY_CHANGED = 1;
+    private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2;
+    private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3;
+
+    private final Handler.Callback mLocalCallback = msg -> {
+        switch (msg.what) {
+            case MSG_BROADCAST_BATTERY_CHANGED: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final Context context;
+                final Intent intent;
+                try {
+                    context = (Context) args.arg1;
+                    intent = (Intent) args.arg2;
+                } finally {
+                    args.recycle();
+                }
+                broadcastBatteryChangedIntent(context, intent, BATTERY_CHANGED_OPTIONS);
+                return true;
+            }
+            case MSG_BROADCAST_POWER_CONNECTION_CHANGED: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final Context context;
+                final Intent intent;
+                try {
+                    context = (Context) args.arg1;
+                    intent = (Intent) args.arg2;
+                } finally {
+                    args.recycle();
+                }
+                sendBroadcastToAllUsers(context, intent, POWER_OPTIONS);
+                return true;
+            }
+            case MSG_BROADCAST_BATTERY_LOW_OKAY: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final Context context;
+                final Intent intent;
+                try {
+                    context = (Context) args.arg1;
+                    intent = (Intent) args.arg2;
+                } finally {
+                    args.recycle();
+                }
+                sendBroadcastToAllUsers(context, intent, BATTERY_OPTIONS);
+                return true;
+            }
+        }
+        return false;
+    };
+
     public BatteryService(Context context) {
         super(context);
 
         mContext = context;
-        mHandler = new Handler(true /*async*/);
+        mHandler = new Handler(mLocalCallback, true /*async*/);
         mLed = new Led(context, getLocalService(LightsManager.class));
         mBatteryStats = BatteryStatsService.getService();
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -660,25 +711,43 @@
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerOptions);
-                    }
-                });
+                if (com.android.server.flags.Flags.consolidateBatteryChangeEvents()) {
+                    mHandler.removeMessages(MSG_BROADCAST_POWER_CONNECTION_CHANGED);
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mContext;
+                    args.arg2 = statusIntent;
+                    mHandler.obtainMessage(MSG_BROADCAST_POWER_CONNECTION_CHANGED, args)
+                            .sendToTarget();
+                } else {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                    POWER_OPTIONS);
+                        }
+                    });
+                }
             }
             else if (mPlugType == 0 && mLastPlugType != 0) {
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerOptions);
-                    }
-                });
+                if (com.android.server.flags.Flags.consolidateBatteryChangeEvents()) {
+                    mHandler.removeMessages(MSG_BROADCAST_POWER_CONNECTION_CHANGED);
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mContext;
+                    args.arg2 = statusIntent;
+                    mHandler.obtainMessage(MSG_BROADCAST_POWER_CONNECTION_CHANGED, args)
+                            .sendToTarget();
+                } else {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                    POWER_OPTIONS);
+                        }
+                    });
+                }
             }
 
             if (shouldSendBatteryLowLocked()) {
@@ -686,26 +755,44 @@
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryOptions);
-                    }
-                });
+                if (com.android.server.flags.Flags.consolidateBatteryChangeEvents()) {
+                    mHandler.removeMessages(MSG_BROADCAST_BATTERY_LOW_OKAY);
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mContext;
+                    args.arg2 = statusIntent;
+                    mHandler.obtainMessage(MSG_BROADCAST_BATTERY_LOW_OKAY, args)
+                            .sendToTarget();
+                } else {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                    BATTERY_OPTIONS);
+                        }
+                    });
+                }
             } else if (mSentLowBatteryBroadcast &&
                     mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryOptions);
-                    }
-                });
+                if (com.android.server.flags.Flags.consolidateBatteryChangeEvents()) {
+                    mHandler.removeMessages(MSG_BROADCAST_BATTERY_LOW_OKAY);
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mContext;
+                    args.arg2 = statusIntent;
+                    mHandler.obtainMessage(MSG_BROADCAST_BATTERY_LOW_OKAY, args)
+                            .sendToTarget();
+                } else {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                    BATTERY_OPTIONS);
+                        }
+                    });
+                }
             }
 
             // We are doing this after sending the above broadcasts, so anything processing
@@ -777,8 +864,16 @@
                     + ", info:" + mHealthInfo.toString());
         }
 
-        mHandler.post(() -> broadcastBatteryChangedIntent(mContext,
-                intent, mBatteryChangedOptions));
+        if (com.android.server.flags.Flags.consolidateBatteryChangeEvents()) {
+            mHandler.removeMessages(MSG_BROADCAST_BATTERY_CHANGED);
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = mContext;
+            args.arg2 = intent;
+            mHandler.obtainMessage(MSG_BROADCAST_BATTERY_CHANGED, args).sendToTarget();
+        } else {
+            mHandler.post(() -> broadcastBatteryChangedIntent(mContext,
+                    intent, BATTERY_CHANGED_OPTIONS));
+        }
     }
 
     private static void broadcastBatteryChangedIntent(Context context, Intent intent,
@@ -1307,6 +1402,12 @@
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private static void sendBroadcastToAllUsers(Context context, Intent intent,
+            Bundle options) {
+        context.sendBroadcastAsUser(intent, UserHandle.ALL, null, options);
+    }
+
     private final class Led {
         // must match: config_notificationsBatteryLowBehavior in config.xml
         static final int LOW_BATTERY_BEHAVIOR_DEFAULT = 0;
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 361b818..fd512a6 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -94,6 +94,8 @@
 275534 notification_unautogrouped (key|3)
 # when a notification is adjusted via assistant
 27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
+# when a notification cancellation is prevented by the system
+27536 notification_cancel_prevented (key|3)
 
 # ---------------------------
 # Watchdog.java
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19279a8..07e5f2e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3527,7 +3527,8 @@
         // of the calling App
         final long token = Binder.clearCallingIdentity();
         try {
-            Context targetAppContext = mContext.createPackageContext(packageName, 0);
+            Context targetAppContext = mContext.createPackageContextAsUser(packageName,
+                    /* flags= */ 0, UserHandle.of(UserHandle.getUserId(originalUid)));
             Intent intent = new Intent(Intent.ACTION_DEFAULT);
             intent.setClassName(packageName,
                     appInfo.manageSpaceActivityName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f1bdc05..d121535 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12158,7 +12158,7 @@
                 opts.dumpProto = true;
             } else if ("--logstats".equals(opt)) {
                 opts.mDumpAllocatorStats = true;
-            } else if ("-h".equals(opt)) {
+            } else if ("-h".equals(opt) || "--help".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
                 pw.println("  -a: include all available information for each process.");
                 pw.println("  -d: include dalvik details.");
@@ -12168,10 +12168,13 @@
                 pw.println("  -p: dump also private dirty memory usage.");
                 pw.println("  --oom: only show processes organized by oom adj.");
                 pw.println("  --local: only collect details locally, don't call process.");
+                pw.println("  --logstats: dump native allocator stats to log");
                 pw.println("  --package: interpret process arg as package, dumping all");
                 pw.println("             processes that have loaded that package.");
                 pw.println("  --checkin: dump data for a checkin");
                 pw.println("  --proto: dump data to proto");
+                pw.println("  --logstats: log native allocator statistics.");
+                pw.println("  --unreachable: dump unreachable native memory with libmemunreachable.");
                 pw.println("If [process] is specified it can be the name or ");
                 pw.println("pid of a specific process to dump.");
                 return;
@@ -16245,43 +16248,55 @@
 
         boolean closeFd = true;
         try {
-            synchronized (mProcLock) {
-                if (fd == null) {
-                    throw new IllegalArgumentException("null fd");
-                }
-                mBinderTransactionTrackingEnabled = false;
+            Objects.requireNonNull(fd);
 
-                PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
-                pw.println("Binder transaction traces for all processes.\n");
-                mProcessList.forEachLruProcessesLOSP(true, process -> {
+            record ProcessToDump(String processName, IApplicationThread thread) { }
+
+            PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
+            pw.println("Binder transaction traces for all processes.\n");
+            final ArrayList<ProcessToDump> processes = new ArrayList<>();
+            synchronized (mProcLock) {
+                // Since dumping binder transactions is a long-running operation, we can't do it
+                // with mProcLock held. Do the initial verification here, and save the processes
+                // to dump later outside the lock.
+                final ArrayList<ProcessRecord> unverifiedProcesses =
+                        new ArrayList<>(mProcessList.getLruProcessesLOSP());
+                for (int i = 0, size = unverifiedProcesses.size(); i < size; i++) {
+                    ProcessRecord process = unverifiedProcesses.get(i);
                     final IApplicationThread thread = process.getThread();
                     if (!processSanityChecksLPr(process, thread)) {
-                        return;
+                        continue;
                     }
-
-                    pw.println("Traces for process: " + process.processName);
-                    pw.flush();
-                    try {
-                        TransferPipe tp = new TransferPipe();
-                        try {
-                            thread.stopBinderTrackingAndDump(tp.getWriteFd());
-                            tp.go(fd.getFileDescriptor());
-                        } finally {
-                            tp.kill();
-                        }
-                    } catch (IOException e) {
-                        pw.println("Failure while dumping IPC traces from " + process +
-                                ".  Exception: " + e);
-                        pw.flush();
-                    } catch (RemoteException e) {
-                        pw.println("Got a RemoteException while dumping IPC traces from " +
-                                process + ".  Exception: " + e);
-                        pw.flush();
-                    }
-                });
-                closeFd = false;
-                return true;
+                    processes.add(new ProcessToDump(process.processName, process.getThread()));
+                }
+                mBinderTransactionTrackingEnabled = false;
             }
+            for (int i = 0, size = processes.size(); i < size; i++) {
+                final String processName = processes.get(i).processName();
+                final IApplicationThread thread = processes.get(i).thread();
+
+                pw.println("Traces for process: " + processName);
+                pw.flush();
+                try {
+                    TransferPipe tp = new TransferPipe();
+                    try {
+                        thread.stopBinderTrackingAndDump(tp.getWriteFd());
+                        tp.go(fd.getFileDescriptor());
+                    } finally {
+                        tp.kill();
+                    }
+                } catch (IOException e) {
+                    pw.println("Failure while dumping IPC traces from " + processName +
+                            ".  Exception: " + e);
+                    pw.flush();
+                } catch (RemoteException e) {
+                    pw.println("Got a RemoteException while dumping IPC traces from " +
+                            processName + ".  Exception: " + e);
+                    pw.flush();
+                }
+            }
+            closeFd = false;
+            return true;
         } finally {
             if (fd != null && closeFd) {
                 try {
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
index 0294ffe..ceba01e4 100644
--- a/services/core/java/com/android/server/am/Android.bp
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -9,3 +9,10 @@
     name: "am_flags_lib",
     aconfig_declarations: "am_flags",
 }
+
+java_aconfig_library {
+    name: "am_flags_host_lib",
+    host_supported: true,
+    libs: ["fake_device_config"],
+    aconfig_declarations: "am_flags",
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5137b4c..8e87342 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -687,6 +687,9 @@
         mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 Flags.streamlinedConnectivityBatteryStats());
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+                BatteryConsumer.POWER_COMPONENT_PHONE,
+                Flags.streamlinedConnectivityBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_WIFI,
                 Flags.streamlinedConnectivityBatteryStats());
@@ -737,6 +740,9 @@
         // By convention POWER_COMPONENT_ANY represents custom Energy Consumers
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_ANY,
                 Flags.streamlinedMiscBatteryStats());
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+                BatteryConsumer.POWER_COMPONENT_ANY,
+                Flags.streamlinedMiscBatteryStats());
 
         mWorker.systemServicesReady();
         mStats.systemServicesReady(mContext);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ab63e24..0e266f5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1201,6 +1201,7 @@
                     >= UNKNOWN_ADJ) {
                     final ProcessServiceRecord psr = app.mServices;
                     switch (state.getCurProcState()) {
+                        case PROCESS_STATE_LAST_ACTIVITY:
                         case PROCESS_STATE_CACHED_ACTIVITY:
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                         case ActivityManager.PROCESS_STATE_CACHED_RECENT:
@@ -2180,7 +2181,6 @@
                 procState = PROCESS_STATE_LAST_ACTIVITY;
                 schedGroup = SCHED_GROUP_BACKGROUND;
                 state.setAdjType("previous-expired");
-                adj = CACHED_APP_MIN_ADJ;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Expire prev adj: " + app);
                 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4c65bd2..8c5152f 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -2307,7 +2307,7 @@
                 return;
             }
 
-            final int userId = mContext.getUserId();
+            final int userId = ActivityManager.getCurrentUser();
             final boolean isNotGame = Arrays.stream(packages).noneMatch(
                     p -> isPackageGame(p, userId));
             synchronized (mUidObserverLock) {
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 2a9dfa2..9317c1e 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -18,24 +18,7 @@
             "name": "FrameworksMockingServicesTests_android_server_appop"
         },
         {
-            "name": "CtsPermissionTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.SplitPermissionTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.PermissionFlagsTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
-                }
-            ]
+            "name": "CtsPermissionTestCases_Platform"
         },
         {
             "name": "CtsAppTestCases",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ca907c5..1cf9935 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1714,6 +1714,10 @@
         sendIILMsg(MSG_IIL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, codec, address, delayMs);
     }
 
+    /*package*/ void setHearingAidTimeout(String address, int delayMs) {
+        sendLMsg(MSG_IL_BT_HEARING_AID_TIMEOUT, SENDMSG_QUEUE, address, delayMs);
+    }
+
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
         synchronized (mDeviceStateLock) {
             mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
@@ -1959,6 +1963,13 @@
                                 (String) msg.obj, msg.arg1, msg.arg2);
                     }
                     break;
+                case MSG_IL_BT_HEARING_AID_TIMEOUT:
+                    // msg.obj  == address of Hearing Aid device
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onMakeHearingAidDeviceUnavailableNow(
+                                (String) msg.obj);
+                    }
+                    break;
                 case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
                     final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
                     final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(
@@ -2234,6 +2245,7 @@
     private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
     private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
     private static final int MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE = 60;
+    private static final int MSG_IL_BT_HEARING_AID_TIMEOUT = 61;
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
@@ -2246,6 +2258,7 @@
             case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_CHECK_MUTE_MUSIC:
+            case MSG_IL_BT_HEARING_AID_TIMEOUT:
                 return true;
             default:
                 return false;
@@ -2330,6 +2343,7 @@
                 case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_IIL_BTLEAUDIO_TIMEOUT:
                 case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
+                case MSG_IL_BT_HEARING_AID_TIMEOUT:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
                         time = sLastDeviceConnectMsgTime + 30;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 082dca6..a9bff8b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1050,6 +1050,11 @@
         }
     }
 
+    /*package*/ void onMakeHearingAidDeviceUnavailableNow(String address) {
+        synchronized (mDevicesLock) {
+            makeHearingAidDeviceUnavailable(address);
+        }
+    }
 
     /**
      * Goes over all connected LE Audio devices in the provided group ID and
@@ -1902,12 +1907,10 @@
                     .set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
                     .record();
             if (toRemove.size() > 0) {
-                /*final int delay = */
-                checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+                final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
                 toRemove.stream().forEach(deviceAddress ->
-                        // TODO delay not used?
-                        makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+                        makeHearingAidDeviceUnavailableLater(deviceAddress, delay)
                 );
             }
         }
@@ -2498,6 +2501,15 @@
         mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
+    @GuardedBy("mDevicesLock")
+    private void makeHearingAidDeviceUnavailableLater(
+            String address, int delayMs) {
+        // the device will be made unavailable later, so consider it disconnected right away
+        mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
+        // send the delayed message to make the device unavailable later
+        mDeviceBroker.setHearingAidTimeout(address, delayMs);
+    }
+
     /**
      * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected.
      * Visibility by APM plays no role
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index c5180af..5283edd 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -33,6 +33,7 @@
 
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -190,6 +191,7 @@
                 mIsUpdateDeferred = true;
                 return;
             }
+            Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "audioserver_permission_update");
             try {
                 for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
                     var newPerms = getUidsHoldingPerm(i);
@@ -203,6 +205,8 @@
                 mDest = null;
                 // We didn't necessarily finish
                 mIsUpdateDeferred = true;
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53b04df..168ec05 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -284,11 +284,16 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 
@@ -785,6 +790,7 @@
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
     private final Executor mAudioServerLifecycleExecutor;
+    private final List<Future> mScheduledPermissionTasks = new ArrayList();
 
     private IMediaProjectionManager mProjectionService; // to validate projection token
 
@@ -1092,7 +1098,8 @@
 
         public Lifecycle(Context context) {
             super(context);
-            var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
+            var audioserverLifecycleExecutor = Executors.newSingleThreadScheduledExecutor(
+                    (Runnable r) -> new Thread(r, "audioserver_lifecycle"));
             var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
             mService = new AudioService(context,
                               AudioSystemAdapter.getDefaultAdapter(),
@@ -1222,34 +1229,6 @@
         mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
         mBroadcastHandlerThread.start();
 
-        // Listen to permission invalidations for the PermissionProvider
-        if (audioserverPermissions()) {
-            final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
-            mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
-                    new Runnable() {
-                        // Roughly chosen to be long enough to suppress the autocork behavior
-                        // of the permission cache (50ms), and longer than the task could reasonably
-                        // take, even with many packages and users, while not introducing visible
-                        // permission leaks - since the app needs to restart, and trigger an action
-                        // which requires permissions from audioserver before this delay.
-                        // For RECORD_AUDIO, we are additionally protected by appops.
-                        final long UPDATE_DELAY_MS = 110;
-                        final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
-                        @Override
-                        public void run() {
-                            var currentTime = SystemClock.uptimeMillis();
-                            if (currentTime > scheduledUpdateTimestamp.get()) {
-                                scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
-                                broadcastHandler.postAtTime( () ->
-                                        mAudioServerLifecycleExecutor.execute(mPermissionProvider
-                                            ::onPermissionStateChanged),
-                                        currentTime + UPDATE_DELAY_MS
-                                    );
-                            }
-                        }
-            });
-        }
-
         mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
 
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1717,8 +1696,10 @@
 
     public void onSystemReady() {
         mSystemReady = true;
+        if (audioserverPermissions()) {
+            setupPermissionListener();
+        }
         scheduleLoadSoundEffects();
-
         mDeviceBroker.onSystemReady();
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -10608,6 +10589,67 @@
         }
     }
 
+    /* Listen to permission invalidations for the PermissionProvider */
+    private void setupPermissionListener() {
+        // Roughly chosen to be long enough to suppress the autocork behavior of the permission
+        // cache (50ms), while not introducing visible permission leaks - since the app needs to
+        // restart, and trigger an action which requires permissions from audioserver before this
+        // delay. For RECORD_AUDIO, we are additionally protected by appops.
+        final long UPDATE_DELAY_MS = 60;
+        // instanceof to simplify the construction requirements of AudioService for testing: no
+        // delayed execution during unit tests.
+        if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) {
+            // The order on the task list is an embedding on the scheduling order of the executor,
+            // since we synchronously add the scheduled task to our local queue. This list should
+            // almost always have only two elements, except in cases of serious system contention.
+            Runnable task = () -> {
+                synchronized (mScheduledPermissionTasks) {
+                    mScheduledPermissionTasks.add(exec.schedule(() -> {
+                        try {
+                            // Our goal is to remove all tasks which don't correspond to ourselves
+                            // on this queue. Either they are already done (ahead of us), or we
+                            // should cancel them (behind us), since their work is redundant after
+                            // we fire.
+                            // We must be the first non-completed task in the queue, since the
+                            // execution order matches the queue order. Note, this task is the only
+                            // writer on elements in the queue, and the task is serialized, so
+                            //  => no in-flight cancellation
+                            //  => exists at least one non-completed task (ourselves)
+                            //  => the queue is non-empty (only completed tasks removed)
+                            synchronized (mScheduledPermissionTasks) {
+                                final var iter = mScheduledPermissionTasks.iterator();
+                                while (iter.next().isDone()) {
+                                    iter.remove();
+                                }
+                                // iter is on the first element which is not completed (us)
+                                while (iter.hasNext()) {
+                                    if (!iter.next().cancel(false)) {
+                                        throw new AssertionError(
+                                                "Cancel should be infallible since we" +
+                                                "cancel from the executor");
+                                    }
+                                    iter.remove();
+                                }
+                            }
+                            mPermissionProvider.onPermissionStateChanged();
+                        } catch (Exception e) {
+                            // Handle executor routing exceptions to nowhere
+                            Thread.getDefaultUncaughtExceptionHandler()
+                                    .uncaughtException(Thread.currentThread(), e);
+                        }
+                    }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS));
+                }
+            };
+            mAudioSystem.listenForSystemPropertyChange(
+                    PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    task);
+        } else {
+            mAudioSystem.listenForSystemPropertyChange(
+                    PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    () -> mAudioServerLifecycleExecutor.execute(
+                                mPermissionProvider::onPermissionStateChanged));
+        }
+    }
 
     //==========================================================================================
     // Audio Focus
@@ -10691,7 +10733,7 @@
         return true;
     }
 
-    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
+    public int requestAudioFocus(AudioAttributes aa, int focusReqType, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName,
             String attributionTag, int flags, IAudioPolicyCallback pcb, int sdk) {
         if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST) != 0) {
@@ -10700,7 +10742,7 @@
         final int uid = Binder.getCallingUid();
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
                 .setUid(uid)
-                //.putInt("durationHint", durationHint)
+                //.putInt("focusReqType", focusReqType)
                 .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
                 .set(MediaMetrics.Property.CLIENT_NAME, clientId)
                 .set(MediaMetrics.Property.EVENT, "requestAudioFocus")
@@ -10760,9 +10802,14 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
+            //TODO move inside HardeningEnforcer after refactor that moves permission checks
+            //     in the blockFocusMethod
+            if (permissionOverridesCheck) {
+                mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid);
+            }
             if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
                     HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
-                    clientId, durationHint, callingPackageName, attributionTag, sdk)) {
+                    clientId, focusReqType, callingPackageName, attributionTag, sdk)) {
                 final String reason = "Audio focus request blocked by hardening";
                 Log.w(TAG, reason);
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, reason).record();
@@ -10773,14 +10820,14 @@
         }
 
         mmi.record();
-        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
+        return mMediaFocusControl.requestAudioFocus(aa, focusReqType, cb, fd,
                 clientId, callingPackageName, flags, sdk,
-                forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/,
+                forceFocusDuckingForAccessibility(aa, focusReqType, uid), -1 /*testUid, ignored*/,
                 permissionOverridesCheck);
     }
 
     /** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */
-    public int requestAudioFocusForTest(AudioAttributes aa, int durationHint, IBinder cb,
+    public int requestAudioFocusForTest(AudioAttributes aa, int focusReqType, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName,
             int flags, int fakeUid, int sdk) {
         if (!enforceQueryAudioStateForTest("focus request")) {
@@ -10791,7 +10838,7 @@
             Log.e(TAG, reason);
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
-        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
+        return mMediaFocusControl.requestAudioFocus(aa, focusReqType, cb, fd,
                 clientId, callingPackageName, flags,
                 sdk, false /*forceDuck*/, fakeUid, true /*permissionOverridesCheck*/);
     }
@@ -14663,6 +14710,24 @@
         return activeAssistantUids;
     }
 
+    @Override
+    /** @see AudioManager#permissionUpdateBarrier() */
+    public void permissionUpdateBarrier() {
+        List<Future> snapshot;
+        synchronized (mScheduledPermissionTasks) {
+            snapshot = List.copyOf(mScheduledPermissionTasks);
+        }
+        for (var x : snapshot) {
+            try {
+                x.get();
+            } catch (CancellationException e) {
+                // Task completed
+            } catch (InterruptedException | ExecutionException e) {
+                Log.wtf(TAG, "Exception which should never occur", e);
+            }
+        }
+    }
+
     List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
         return mDeviceBroker.getDeviceIdentityAddresses(device);
     }
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index 8ae04ac..faeba5d 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -31,7 +31,9 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.modules.expresslog.Counter;
 import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
@@ -55,6 +57,30 @@
     final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS,
             "Hardening enforcement");
 
+    // capacity = 4 for each of the focus request types
+    static final SparseArray<String> METRIC_COUNTERS_FOCUS_DENIAL = new SparseArray<>(4);
+    static final SparseArray<String> METRIC_COUNTERS_FOCUS_GRANT = new SparseArray<>(4);
+
+    static {
+        METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN,
+                "media_audio.value_audio_focus_gain_granted");
+        METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+                "media_audio.value_audio_focus_gain_transient_granted");
+        METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                "media_audio.value_audio_focus_gain_transient_duck_granted");
+        METRIC_COUNTERS_FOCUS_GRANT.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+                "media_audio.value_audio_focus_gain_transient_excl_granted");
+
+        METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN,
+                "media_audio.value_audio_focus_gain_appops_denial");
+        METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+                "media_audio.value_audio_focus_gain_transient_appops_denial");
+        METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                "media_audio.value_audio_focus_gain_transient_duck_appops_denial");
+        METRIC_COUNTERS_FOCUS_DENIAL.put(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+                "media_audio.value_audio_focus_gain_transient_excl_appops_denial");
+    }
+
     /**
      * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
      */
@@ -129,41 +155,61 @@
      * Checks whether the call in the current thread should be allowed or blocked
      * @param focusMethod name of the method to check, for logging purposes
      * @param clientId id of the requester
-     * @param durationHint focus type being requested
+     * @param focusReqType focus type being requested
      * @param attributionTag attribution of the caller
      * @param targetSdk target SDK of the caller
      * @return false if the method call is allowed, true if it should be a no-op
      */
     @SuppressWarnings("AndroidFrameworkCompatChange")
     protected boolean blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId,
-            int durationHint, @NonNull String packageName, String attributionTag, int targetSdk) {
+            int focusReqType, @NonNull String packageName, String attributionTag, int targetSdk) {
         if (packageName.isEmpty()) {
             packageName = getPackNameForUid(callingUid);
         }
 
+        boolean blocked = true;
         if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
             if (DEBUG) {
                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
             }
-            return false;
+            blocked = false;
         } else if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
             if (DEBUG) {
                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk="
                         + targetSdk);
             }
+            blocked = false;
+        }
+
+        metricsLogFocusReq(blocked, focusReqType, callingUid);
+
+        if (!blocked) {
             return false;
         }
 
         String errorMssg = "Focus request DENIED for uid:" + callingUid
-                + " clientId:" + clientId + " req:" + durationHint
+                + " clientId:" + clientId + " req:" + focusReqType
                 + " procState:" + mActivityManager.getUidProcessState(callingUid);
-
-        // TODO metrics
         mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG);
 
         return true;
     }
 
+    /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+        final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
+                : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
+        if (TextUtils.isEmpty(metricId)) {
+            Slog.e(TAG, "Bad string for focus metrics gain:" + focusReq + " blocked:" + blocked);
+            return;
+        }
+        try {
+            Counter.logIncrementWithUid(metricId, callingUid);
+        } catch (Exception e) {
+            Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
+                    + " from uid:" + callingUid, e);
+        }
+    }
+
     private String getPackNameForUid(int uid) {
         final long token = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index dc79ab2..643f330 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -900,8 +900,10 @@
 
         try {
             if (!isAbsoluteVolume) {
-                mLogger.enqueue(
-                        SoundDoseEvent.getAbsVolumeAttenuationEvent(/*attenuation=*/0.f, device));
+                if (mSafeMediaVolumeDevices.indexOfKey(device) >= 0) {
+                    mLogger.enqueue(SoundDoseEvent.getAbsVolumeAttenuationEvent(/*attenuation=*/0.f,
+                            device));
+                }
                 // remove any possible previous attenuation
                 soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
 
@@ -912,8 +914,12 @@
                     && safeDevicesContains(device)) {
                 float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                         (newIndex + 5) / 10, device);
-                mLogger.enqueue(
-                        SoundDoseEvent.getAbsVolumeAttenuationEvent(attenuationDb, device));
+
+                if (mSafeMediaVolumeDevices.indexOfKey(device) >= 0) {
+                    mLogger.enqueue(
+                            SoundDoseEvent.getAbsVolumeAttenuationEvent(attenuationDb, device));
+                }
+
                 soundDose.updateAttenuation(attenuationDb, device);
             }
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 276ab03..abfbddc 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -221,7 +221,8 @@
         mFingerprintSensorProperties = fingerprintSensorProperties;
         mCancelled = false;
         mBiometricFrameworkStatsLogger = logger;
-        mOperationContext = new OperationContextExt(true /* isBP */);
+        mOperationContext = new OperationContextExt(true /* isBP */,
+                preAuthInfo.getIsMandatoryBiometricsAuthentication() /* isMandatoryBiometrics */);
         mBiometricManager = mContext.getSystemService(BiometricManager.class);
 
         mSfpsSensorIds = mFingerprintSensorProperties.stream().filter(
@@ -285,7 +286,8 @@
             sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
                     mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
                     mPromptInfo.isAllowBackgroundAuthentication(),
-                    mPromptInfo.isForLegacyFingerprintManager());
+                    mPromptInfo.isForLegacyFingerprintManager(),
+                    mOperationContext.getIsMandatoryBiometrics());
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 42dd36a..c7532d4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -107,12 +107,13 @@
     void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
             int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
             long requestId, int cookie, boolean allowBackgroundAuthentication,
-            boolean isForLegacyFingerprintManager)
+            boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
             throws RemoteException {
         mCookie = cookie;
         impl.prepareForAuthentication(requireConfirmation, token,
                 sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
-                allowBackgroundAuthentication, isForLegacyFingerprintManager);
+                allowBackgroundAuthentication, isForLegacyFingerprintManager,
+                isMandatoryBiometrics);
         mSensorState = STATE_WAITING_FOR_COOKIE;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index f0da67b..eaf4f81 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -447,6 +447,12 @@
                         getInternalStatus().second));
     }
 
+    /** Returns if mandatory biometrics authentication is in effect */
+    boolean getIsMandatoryBiometricsAuthentication() {
+        return mIsMandatoryBiometricsAuthentication;
+    }
+
+
     /**
      * For the given request, generate the appropriate reason why authentication cannot be started.
      * Note that for some errors, modality is intentionally cleared.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 2c52e3d..f5af5ea 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -78,6 +78,7 @@
     public void authenticate(OperationContextExt operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
             int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
+        Slog.d(TAG, "authenticate logging " + operationContext.getIsMandatoryBiometrics());
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                 statsModality,
                 targetUserId,
@@ -98,7 +99,8 @@
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
                 toProtoWakeReason(operationContext),
-                toProtoWakeReasonDetails(operationContext));
+                toProtoWakeReasonDetails(operationContext),
+                operationContext.getIsMandatoryBiometrics());
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -129,6 +131,7 @@
     public void error(OperationContextExt operationContext,
             int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
             int error, int vendorCode, int targetUserId) {
+        Slog.d(TAG, "error logging " + operationContext.getIsMandatoryBiometrics());
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
                 statsModality,
                 targetUserId,
@@ -149,7 +152,8 @@
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
                 toProtoWakeReason(operationContext),
-                toProtoWakeReasonDetails(operationContext));
+                toProtoWakeReasonDetails(operationContext),
+                operationContext.getIsMandatoryBiometrics());
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index da4e515..4df63e2 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -50,21 +50,33 @@
     @Surface.Rotation private int mOrientation = Surface.ROTATION_0;
     private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
     private final boolean mIsBP;
+    private final boolean mIsMandatoryBiometrics;
 
     /** Create a context. */
     public OperationContextExt(boolean isBP) {
         this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE);
     }
 
-    public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality) {
-        this(new OperationContext(), isBP, modality);
+    public OperationContextExt(boolean isBP, boolean isMandatoryBiometrics) {
+        this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE, isMandatoryBiometrics);
+    }
+
+    public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality,
+            boolean isMandatoryBiometrics) {
+        this(new OperationContext(), isBP, modality, isMandatoryBiometrics);
     }
 
     /** Create a wrapped context. */
     public OperationContextExt(@NonNull OperationContext context, boolean isBP,
             @BiometricAuthenticator.Modality int modality) {
+        this(context, isBP, modality, false /* isMandatoryBiometrics */);
+    }
+
+    public OperationContextExt(@NonNull OperationContext context, boolean isBP,
+            @BiometricAuthenticator.Modality int modality, boolean isMandatoryBiometrics) {
         mAidlContext = context;
         mIsBP = isBP;
+        mIsMandatoryBiometrics = isMandatoryBiometrics;
 
         if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
             mAidlContext.operationState = OperationState.fingerprintOperationState(
@@ -285,6 +297,11 @@
         return mAidlContext.operationState;
     }
 
+    /** If mandatory biometrics is active. */
+    public boolean getIsMandatoryBiometrics() {
+        return mIsMandatoryBiometrics;
+    }
+
     /** Update this object with the latest values from the given context. */
     OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
         mAidlContext.isAod = biometricContext.isAod();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index fbd32a6..04522e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -59,9 +59,10 @@
     public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            boolean isMandatoryBiometrics) {
         super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
-                logger, biometricContext);
+                logger, biometricContext, isMandatoryBiometrics);
         mPowerManager = context.getSystemService(PowerManager.class);
         mShouldVibrate = shouldVibrate;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index daaafcb..09386ae28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -99,7 +99,7 @@
             boolean shouldVibrate, int sensorStrength) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate,
-                biometricLogger, biometricContext);
+                biometricLogger, biometricContext, options.isMandatoryBiometrics());
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 438367d..32c0bd3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -60,7 +60,7 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                shouldVibrate, logger, biometricContext);
+                shouldVibrate, logger, biometricContext, false /* isMandatoryBiometrics */);
         mBiometricUtils = utils;
         mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
         mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 2adf0cb..b573b56 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -37,7 +37,7 @@
             int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                biometricLogger, biometricContext);
+                biometricLogger, biometricContext, false /* isMandatoryBiometrics */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 0f01510..3bc51a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -41,26 +41,29 @@
     private final OperationContextExt mOperationContext;
 
     /**
-     * @param context    system_server context
-     * @param lazyDaemon pointer for lazy retrieval of the HAL
-     * @param token      a unique token for the client
-     * @param listener   recipient of related events (e.g. authentication)
-     * @param userId     target user id for operation
-     * @param owner      name of the client that owns this
-     * @param cookie     BiometricPrompt authentication cookie (to be moved into a subclass soon)
-     * @param sensorId   ID of the sensor that the operation should be requested of
-     * @param biometricLogger framework stats logger
+     * @param context          system_server context
+     * @param lazyDaemon       pointer for lazy retrieval of the HAL
+     * @param token            a unique token for the client
+     * @param listener         recipient of related events (e.g. authentication)
+     * @param userId           target user id for operation
+     * @param owner            name of the client that owns this
+     * @param cookie           BiometricPrompt authentication cookie (to be moved into a subclass
+     *                         soon)
+     * @param sensorId         ID of the sensor that the operation should be requested of
+     * @param biometricLogger  framework stats logger
      * @param biometricContext system context metadata
      */
     public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int cookie, int sensorId,
-            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+            @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+            boolean isMandatoryBiometrics) {
         super(context, token, listener, userId, owner, cookie, sensorId,
                 biometricLogger, biometricContext);
         mLazyDaemon = lazyDaemon;
         int modality = listener != null ? listener.getModality() : BiometricAuthenticator.TYPE_NONE;
-        mOperationContext = new OperationContextExt(isBiometricPrompt(), modality);
+        mOperationContext = new OperationContextExt(isBiometricPrompt(), modality,
+                isMandatoryBiometrics);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 7bd905b..6c30559 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -143,7 +143,8 @@
             @NonNull BiometricUtils<S> utils,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
-                userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
+                userId, owner, 0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 81ab26d..2c2c685 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -55,7 +55,8 @@
         // Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
         // is all done internally.
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+                0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mEnrolledList = enrolledList;
         mInitialEnrolledSize = mEnrolledList.size();
         mUtils = utils;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index d5aa5e2..6c93366 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -49,7 +49,7 @@
             @NonNull IInvalidationCallback callback) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId,
                 context.getOpPackageName(), 0 /* cookie */, sensorId,
-                logger, biometricContext);
+                logger, biometricContext, false /* isMandatoryBiometrics */);
         mAuthenticatorIds = authenticatorIds;
         mInvalidationCallback = callback;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index d2ef278..ad5877a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -49,7 +49,7 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                logger, biometricContext);
+                logger, biometricContext, false /* isMandatoryBiometrics */);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 88f4da2..0c8a2dd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -32,7 +32,8 @@
             @NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
         super(context, lazyDaemon, token, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, biometricLogger, biometricContext);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext,
+                false /* isMandatoryBiometrics */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 21c9f64..ff694cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -51,7 +51,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStartedCallback<U> callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, logger, biometricContext);
+                0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mUserStartedCallback = callback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index e01c4ec..9119bc7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -54,7 +54,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
         super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
-                0 /* cookie */, sensorId, logger, biometricContext);
+                0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mUserStoppedCallback = callback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 2211003..67d7505 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -63,13 +63,14 @@
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
-            boolean isForLegacyFingerprintManager)
+            boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
             throws RemoteException {
         mFaceService.prepareForAuthentication(requireConfirmation, token, operationId,
                 sensorReceiver, new FaceAuthenticateOptions.Builder()
                         .setUserId(userId)
                         .setSensorId(mSensorId)
                         .setOpPackageName(opPackageName)
+                        .setIsMandatoryBiometrics(isMandatoryBiometrics)
                         .build(),
                 requestId, cookie, allowBackgroundAuthentication);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index cf677d5..7b1186c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @NonNull
+    private final FaceUtils mBiometricUtils;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
             @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+            @NonNull FaceUtils biometricUtils) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
@@ -95,6 +98,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+        mBiometricUtils = biometricUtils;
     }
 
     @Override
@@ -167,8 +171,7 @@
         } else {
             currentUserId = client.getTargetUserId();
         }
-        final CharSequence name = FaceUtils.getInstance(mSensorId)
-                .getUniqueName(mContext, currentUserId);
+        final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
         final Face face = new Face(name, enrollmentId, mSensorId);
 
         handleResponse(FaceEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 8b4da31..8eb62eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -83,7 +83,8 @@
             boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
-                false /* shouldVibrate */, logger, biometricContext);
+                false /* shouldVibrate */, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         setRequestId(requestId);
         mAuthenticationStateListeners = authenticationStateListeners;
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 3eecc6d..d4ec573 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -60,7 +60,6 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.face.FaceService;
-import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -85,6 +84,7 @@
     private final int mMaxTemplatesPerUser;
     private final boolean mDebugConsent;
     private final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason;
+    private final BiometricUtils<Face> mBiometricUtils;
 
     private final ClientMonitorCallback mPreviewHandleDeleterCallback =
             new ClientMonitorCallback() {
@@ -107,7 +107,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             int maxTemplatesPerUser, boolean debugConsent,
             android.hardware.face.FaceEnrollOptions options,
-            @NonNull AuthenticationStateListeners authenticationStateListeners) {
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
+            @NonNull BiometricUtils<Face> biometricUtils) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
                 timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
                 BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
@@ -122,6 +123,7 @@
         mDebugConsent = debugConsent;
         mDisabledFeatures = disabledFeatures;
         mPreviewSurface = previewSurface;
+        mBiometricUtils = biometricUtils;
         Slog.w(TAG, "EnrollOptions "
                 + android.hardware.face.FaceEnrollOptions.enrollReasonToString(
                         options.getEnrollReason()));
@@ -144,7 +146,7 @@
 
     @Override
     protected boolean hasReachedEnrollmentLimit() {
-        return FaceUtils.getInstance(getSensorId()).getBiometricsForUser(getContext(),
+        return mBiometricUtils.getBiometricsForUser(getContext(),
                 getTargetUserId()).size() >= mMaxTemplatesPerUser;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 1f4f612..dbd293c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -42,7 +42,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
-                0 /* cookie */, sensorId, logger, biometricContext);
+                0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c41b706..8d1336f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -55,7 +55,7 @@
             @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext, int feature) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                logger, biometricContext);
+                logger, biometricContext, false /* isMandatoryBiometrics */);
         mUserId = userId;
         mFeature = feature;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index 964bf6c..c27b7c4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -30,7 +30,6 @@
 import com.android.server.biometrics.sensors.InternalCleanupClient;
 import com.android.server.biometrics.sensors.InternalEnumerateClient;
 import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.List;
 import java.util.Map;
@@ -75,7 +74,7 @@
     @Override
     protected void onAddUnknownTemplate(int userId,
             @NonNull BiometricAuthenticator.Identifier identifier) {
-        FaceUtils.getInstance(getSensorId()).addBiometricForUser(
+        mBiometricUtils.addBiometricForUser(
                 getContext(), getTargetUserId(), (Face) identifier);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index f0a4189..bb213bf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -72,7 +72,6 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.face.FaceUtils;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
 import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
@@ -326,8 +325,8 @@
         }
 
         if (Build.isDebuggable()) {
-            BiometricUtils<Face> utils = FaceUtils.getInstance(
-                    mFaceSensors.keyAt(0));
+            BiometricUtils<Face> utils = mFaceSensors.get(
+                    mFaceSensors.keyAt(0)).getFaceUtilsInstance();
             for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id);
                 Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -386,7 +385,7 @@
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
                             BiometricLogger.ofUnknown(mContext),
                             mBiometricContext,
-                            FaceUtils.getInstance(sensorId));
+                            mFaceSensors.get(sensorId).getFaceUtilsInstance());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -415,7 +414,8 @@
     @NonNull
     @Override
     public List<Face> getEnrolledFaces(int sensorId, int userId) {
-        return FaceUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+        return mFaceSensors.get(sensorId).getFaceUtilsInstance()
+                .getBiometricsForUser(mContext, userId);
     }
 
     @Override
@@ -497,13 +497,14 @@
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
-                    ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+                    opPackageName, id, mFaceSensors.get(sensorId).getFaceUtilsInstance(),
+                    disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent, options,
-                    mAuthenticationStateListeners);
+                    mAuthenticationStateListeners,
+                    mFaceSensors.get(sensorId).getFaceUtilsInstance());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
         return id;
@@ -615,7 +616,7 @@
     @Override
     public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        final List<Face> faces = FaceUtils.getInstance(sensorId)
+        final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
                 .getBiometricsForUser(mContext, userId);
         final int[] faceIds = new int[faces.size()];
         for (int i = 0; i < faces.size(); i++) {
@@ -632,7 +633,7 @@
             final FaceRemovalClient client = new FaceRemovalClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
-                    opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+                    opPackageName, mFaceSensors.get(sensorId).getFaceUtilsInstance(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN,
                             mAuthenticationStatsCollector),
@@ -666,7 +667,7 @@
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
             mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
-            final List<Face> faces = FaceUtils.getInstance(sensorId)
+            final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
                 Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId);
@@ -687,7 +688,7 @@
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
         mHandler.post(() -> {
             mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
-            final List<Face> faces = FaceUtils.getInstance(sensorId)
+            final List<Face> faces = mFaceSensors.get(sensorId).getFaceUtilsInstance()
                     .getBiometricsForUser(mContext, userId);
             if (faces.isEmpty()) {
                 Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId);
@@ -727,7 +728,7 @@
                                     BiometricsProtoEnums.CLIENT_UNKNOWN,
                                     mAuthenticationStatsCollector),
                             mBiometricContext,
-                            FaceUtils.getInstance(sensorId),
+                            mFaceSensors.get(sensorId).getFaceUtilsInstance(),
                             mFaceSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
@@ -768,7 +769,7 @@
             JSONArray sets = new JSONArray();
             for (UserInfo user : UserManager.get(mContext).getUsers()) {
                 final int userId = user.getUserHandle().getIdentifier();
-                final int c = FaceUtils.getInstance(sensorId)
+                final int c = mFaceSensors.get(sensorId).getFaceUtilsInstance()
                         .getBiometricsForUser(mContext, userId).size();
                 JSONObject set = new JSONObject();
                 set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index d02eefa..93bc1f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -60,7 +60,8 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, logger, biometricContext);
+                0 /* cookie */, sensorId, logger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutTracker = lockoutTracker;
         mLockoutResetDispatcher = lockoutResetDispatcher;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index f6da872..fc73e58 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -52,7 +52,7 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             int feature, boolean enabled, byte[] hardwareAuthToken) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
-                logger, biometricContext);
+                logger, biometricContext, false /* isMandatoryBiometrics */);
         mFeature = feature;
         mEnabled = enabled;
         mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b0e7575..6f95349 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -158,7 +158,7 @@
                                         Slog.e(TAG, "Face sensor hardware unavailable.");
                                         mCurrentSession = null;
                                     }
-                                });
+                                }, getFaceUtilsInstance());
 
                         return Sensor.this.getStartUserClient(resultController, sensorId,
                                 newUserId, provider);
@@ -280,8 +280,7 @@
             final long userToken = proto.start(SensorStateProto.USER_STATES);
             proto.write(UserStateProto.USER_ID, userId);
             proto.write(UserStateProto.NUM_ENROLLED,
-                    FaceUtils.getInstance(mSensorProperties.sensorId)
-                            .getBiometricsForUser(mContext, userId).size());
+                    getFaceUtilsInstance().getBiometricsForUser(mContext, userId).size());
             proto.end(userToken);
         }
 
@@ -358,4 +357,8 @@
             Supplier<AidlSession> lazySession) {
         mLazySession = lazySession;
     }
+
+    public FaceUtils getFaceUtilsInstance() {
+        return FaceUtils.getInstance(mSensorProperties.sensorId);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index 9a4c29d..444a6d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -159,6 +159,11 @@
     }
 
     @Override
+    public FaceUtils getFaceUtilsInstance() {
+        return FaceUtils.getLegacyInstance(getSensorProperties().sensorId);
+    }
+
+    @Override
     protected LockoutTracker getLockoutTracker(boolean forAuth) {
         return mLockoutTracker;
     }
@@ -180,7 +185,8 @@
                 mLockoutTracker,
                 mLockoutResetDispatcher,
                 mAuthSessionCoordinator,
-                mAidlResponseHandlerCallback);
+                mAidlResponseHandlerCallback,
+                getFaceUtilsInstance());
     }
 
     private IBiometricsFace getIBiometricsFace() {
@@ -247,8 +253,7 @@
         return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
                 mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
                 BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
-                !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
-                        getContext(), userId).isEmpty(),
+                !getFaceUtilsInstance().getBiometricsForUser(getContext(), userId).isEmpty(),
                 getAuthenticatorIds());
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index b6fa0c1..d50ab8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -63,13 +63,14 @@
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
-            boolean isForLegacyFingerprintManager)
+            boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
             throws RemoteException {
         mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver,
                 new FingerprintAuthenticateOptions.Builder()
                         .setSensorId(mSensorId)
                         .setUserId(userId)
                         .setOpPackageName(opPackageName)
+                        .setIsMandatoryBiometrics(isMandatoryBiometrics)
                         .build(),
                 requestId, cookie, allowBackgroundAuthentication, isForLegacyFingerprintManager);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 6d1715f..80b7cde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -80,13 +80,16 @@
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @NonNull
     private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @NonNull
+    private final FingerprintUtils mBiometricUtils;
 
     public AidlResponseHandler(@NonNull Context context,
             @NonNull BiometricScheduler scheduler, int sensorId, int userId,
             @NonNull LockoutTracker lockoutTracker,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
-            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+            @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback,
+            @NonNull FingerprintUtils biometricUtils) {
         mContext = context;
         mScheduler = scheduler;
         mSensorId = sensorId;
@@ -95,6 +98,7 @@
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mAuthSessionCoordinator = authSessionCoordinator;
         mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
+        mBiometricUtils = biometricUtils;
     }
 
     @Override
@@ -158,8 +162,7 @@
         } else {
             currentUserId = client.getTargetUserId();
         }
-        final CharSequence name = FingerprintUtils.getInstance(mSensorId)
-                .getUniqueName(mContext, currentUserId);
+        final CharSequence name = mBiometricUtils.getUniqueName(mContext, currentUserId);
         final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
                 enrollmentId, mSensorId);
         handleResponse(FingerprintEnrollClient.class, (c) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index fb48053..a81ab8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -71,7 +71,8 @@
             boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
-                true /* shouldVibrate */, biometricLogger, biometricContext);
+                true /* shouldVibrate */, biometricLogger, biometricContext,
+                false /* isMandatoryBiometrics */);
         setRequestId(requestId);
         mAuthenticationStateListeners = authenticationStateListeners;
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 0353969..f77e116 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -42,7 +42,8 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             Map<Integer, Long> authenticatorIds) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, biometricLogger, biometricContext);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mAuthenticatorIds = authenticatorIds;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 1fc5179..40b8a45 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -81,7 +81,7 @@
     @Override
     protected void onAddUnknownTemplate(int userId,
             @NonNull BiometricAuthenticator.Identifier identifier) {
-        FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
+        mBiometricUtils.addBiometricForUser(
                 getContext(), getTargetUserId(), (Fingerprint) identifier);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 12baf00..9edaa4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -79,7 +79,6 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorList;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
@@ -354,8 +353,9 @@
         }
 
         if (Build.isDebuggable()) {
-            BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance(
-                    mFingerprintSensors.keyAt(0));
+            final int sensorId = mFingerprintSensors.keyAt(0);
+            final BiometricUtils<Fingerprint> utils = mFingerprintSensors.get(sensorId)
+                    .getFingerprintUtilsInstance();
             for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
                 List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id);
                 Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": "
@@ -442,7 +442,7 @@
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
                             BiometricLogger.ofUnknown(mContext),
                             mBiometricContext,
-                            FingerprintUtils.getInstance(sensorId));
+                            mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -507,7 +507,7 @@
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, FingerprintUtils.getInstance(sensorId),
+                    opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
                     sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
@@ -638,8 +638,8 @@
     public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int userId,
             @NonNull String opPackageName) {
-        final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId)
-                .getBiometricsForUser(mContext, userId);
+        final List<Fingerprint> fingers = mFingerprintSensors.get(sensorId)
+                .getFingerprintUtilsInstance().getBiometricsForUser(mContext, userId);
         final int[] fingerIds = new int[fingers.size()];
         for (int i = 0; i < fingers.size(); i++) {
             fingerIds[i] = fingers.get(i).getBiometricId();
@@ -655,11 +655,10 @@
             final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
-                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                    createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN,
-                            mAuthenticationStatsCollector),
-                    mBiometricContext,
+                    opPackageName, mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
+                    sensorId, createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                    mAuthenticationStatsCollector), mBiometricContext,
                     mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -683,7 +682,7 @@
                                     BiometricsProtoEnums.CLIENT_UNKNOWN,
                                     mAuthenticationStatsCollector),
                             mBiometricContext,
-                            FingerprintUtils.getInstance(sensorId),
+                            mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance(),
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
@@ -706,14 +705,15 @@
 
     @Override
     public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
-        FingerprintUtils.getInstance(sensorId)
+        mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
                 .renameBiometricForUser(mContext, userId, fingerId, name);
     }
 
     @NonNull
     @Override
     public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
-        return FingerprintUtils.getInstance(sensorId).getBiometricsForUser(mContext, userId);
+        return mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
+                .getBiometricsForUser(mContext, userId);
     }
 
     @Override
@@ -842,7 +842,7 @@
             JSONArray sets = new JSONArray();
             for (UserInfo user : UserManager.get(mContext).getUsers()) {
                 final int userId = user.getUserHandle().getIdentifier();
-                final int c = FingerprintUtils.getInstance(sensorId)
+                final int c = mFingerprintSensors.get(sensorId).getFingerprintUtilsInstance()
                         .getBiometricsForUser(mContext, userId).size();
                 JSONObject set = new JSONObject();
                 set.put("id", userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 387ae12..81a3d83 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -59,7 +59,8 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
-                0 /* cookie */, sensorId, biometricLogger, biometricContext);
+                0 /* cookie */, sensorId, biometricLogger, biometricContext,
+                false /* isMandatoryBiometrics */);
         mHardwareAuthToken = hardwareAuthToken == null ? null :
                 HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
         mLockoutCache = lockoutTracker;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 1c6dfe0..d12d7b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -170,7 +170,7 @@
                                                 "Fingerprint sensor hardware unavailable.");
                                         mCurrentSession = null;
                                     }
-                                });
+                                }, getFingerprintUtilsInstance());
 
                         return Sensor.this.getStartUserClient(resultController, sensorId,
                                 newUserId);
@@ -187,7 +187,7 @@
                             + halInterfaceVersion);
                     mCurrentSession = new AidlSession(halInterfaceVersion,
                             newSession, userIdStarted, resultController);
-                    if (FingerprintUtils.getInstance(sensorId)
+                    if (getFingerprintUtilsInstance()
                             .isInvalidationInProgress(mContext, userIdStarted)) {
                         Slog.w(TAG,
                                 "Scheduling unfinished invalidation request for "
@@ -307,9 +307,8 @@
 
             final long userToken = proto.start(SensorStateProto.USER_STATES);
             proto.write(UserStateProto.USER_ID, userId);
-            proto.write(UserStateProto.NUM_ENROLLED,
-                    FingerprintUtils.getInstance(mSensorProperties.sensorId)
-                            .getBiometricsForUser(mContext, userId).size());
+            proto.write(UserStateProto.NUM_ENROLLED, getFingerprintUtilsInstance()
+                    .getBiometricsForUser(mContext, userId).size());
             proto.end(userToken);
         }
 
@@ -386,4 +385,8 @@
     public FingerprintProvider getProvider() {
         return mProvider;
     }
+
+    public FingerprintUtils getFingerprintUtilsInstance() {
+        return FingerprintUtils.getInstance(mSensorProperties.sensorId);
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 3214b6d..8f52d00 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -148,6 +148,11 @@
     }
 
     @Override
+    public FingerprintUtils getFingerprintUtilsInstance() {
+        return FingerprintUtils.getLegacyInstance(getSensorProperties().sensorId);
+    }
+
+    @Override
     @Nullable
     @VisibleForTesting
     protected AidlSession getSessionForUser(int userId) {
@@ -186,7 +191,8 @@
                 mLockoutTracker,
                 mLockoutResetDispatcher,
                 mAuthSessionCoordinator,
-                mAidlResponseHandlerCallback);
+                mAidlResponseHandlerCallback,
+                getFingerprintUtilsInstance());
     }
 
     @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
@@ -266,8 +272,7 @@
                 () -> getSession().getSession(), newUserId, TAG,
                 getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
                 getBiometricContext(), () -> mCurrentUserId,
-                !FingerprintUtils.getInstance(getSensorProperties().sensorId)
-                        .getBiometricsForUser(getContext(),
+                !getFingerprintUtilsInstance().getBiometricsForUser(getContext(),
                 newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
                 mUserStartedCallback);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 01d1e378..76b5c4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -60,7 +60,7 @@
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication,
-            boolean isForLegacyFingerprintManager)
+            boolean isForLegacyFingerprintManager, boolean isMandatoryBiometrics)
             throws RemoteException {
     }
 
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index c31d1d8..d909004 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1500,10 +1500,18 @@
         }
 
         private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
-            Vote vote = info != null && info.layoutLimitedRefreshRate != null
-                    ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
-                    info.layoutLimitedRefreshRate.max) : null;
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+            Vote refreshRateVote = null;
+            Vote frameRateVote = null;
+            if (info != null && info.layoutLimitedRefreshRate != null) {
+                refreshRateVote = Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
+                        info.layoutLimitedRefreshRate.max);
+                frameRateVote = Vote.forRenderFrameRates(info.layoutLimitedRefreshRate.min,
+                        info.layoutLimitedRefreshRate.max);
+            }
+            mVotesStorage.updateVote(
+                    displayId, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE, refreshRateVote);
+            mVotesStorage.updateVote(
+                    displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, frameRateVote);
         }
 
         private void removeUserSettingDisplayPreferredSize(int displayId) {
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 88ee04481..459f9a6 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -110,37 +110,40 @@
     int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 14;
+    int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14;
+
+    // For concurrent displays we want to limit refresh rate on all displays
+    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15;
 
     // For internal application to limit display modes to specific ids
-    int PRIORITY_SYSTEM_REQUESTED_MODES = 15;
+    int PRIORITY_SYSTEM_REQUESTED_MODES = 16;
 
     // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
     // Settings.Global.LOW_POWER_MODE is on.
     // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
     // higher priority votes), render rate limit can still apply
-    int PRIORITY_LOW_POWER_MODE_MODES = 16;
+    int PRIORITY_LOW_POWER_MODE_MODES = 17;
 
     // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 17;
+    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 18;
+    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    int PRIORITY_SKIN_TEMPERATURE = 19;
+    int PRIORITY_SKIN_TEMPERATURE = 20;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    int PRIORITY_PROXIMITY = 20;
+    int PRIORITY_PROXIMITY = 21;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    int PRIORITY_UDFPS = 21;
+    int PRIORITY_UDFPS = 22;
 
     @IntDef(prefix = { "PRIORITY_" }, value = {
             PRIORITY_DEFAULT_RENDER_FRAME_RATE,
@@ -157,6 +160,7 @@
             PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE,
             PRIORITY_LIMIT_MODE,
             PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE,
+            PRIORITY_LAYOUT_LIMITED_REFRESH_RATE,
             PRIORITY_LAYOUT_LIMITED_FRAME_RATE,
             PRIORITY_SYSTEM_REQUESTED_MODES,
             PRIORITY_LOW_POWER_MODE_MODES,
@@ -283,6 +287,8 @@
                 return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
             case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
                 return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
+            case PRIORITY_LAYOUT_LIMITED_REFRESH_RATE:
+                return "PRIORITY_LAYOUT_LIMITED_REFRESH_RATE";
             case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
                 return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
             case PRIORITY_SYSTEM_REQUESTED_MODES:
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index c361aee..649678c 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -45,3 +45,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "consolidate_battery_change_events"
+    description: "Optimize battery status updates by delivering only the most recent battery information"
+    bug: "361334584"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bbe7b2b..3c7b9d3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4328,6 +4328,7 @@
         HdmiCecLocalDevicePlayback playback = playback();
         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
         if (playback != null) {
+            playback.dismissUiOnActiveSourceStatusRecovered();
             playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
                     caller);
             playback.wakeUpIfActiveSource();
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d0b8990..f44b852 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -142,7 +142,6 @@
             ERROR_KEYSTORE_FAILURE,
             ERROR_NO_NETWORK,
             ERROR_TIMEOUT_EXHAUSTED,
-            ERROR_NO_REBOOT_ESCROW_DATA,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface RebootEscrowErrorCode {
@@ -158,7 +157,6 @@
     static final int ERROR_KEYSTORE_FAILURE = 7;
     static final int ERROR_NO_NETWORK = 8;
     static final int ERROR_TIMEOUT_EXHAUSTED = 9;
-    static final int ERROR_NO_REBOOT_ESCROW_DATA = 10;
 
     private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
 
@@ -507,9 +505,6 @@
         if (rebootEscrowUsers.isEmpty()) {
             Slog.i(TAG, "No reboot escrow data found for users,"
                     + " skipping loading escrow data");
-            setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler);
-            reportMetricOnRestoreComplete(
-                    /* success= */ false, /* attemptCount= */ 1, retryHandler);
             clearMetricsStorage();
             return;
         }
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index e5abb44..4fa7112 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -122,8 +122,9 @@
             getNotificationShadeSections();
 
     private static List<NotificationSectioner> getNotificationShadeSections() {
+        ArrayList<NotificationSectioner> sectionsList = new ArrayList<>();
         if (android.service.notification.Flags.notificationClassification()) {
-            return List.of(
+            sectionsList.addAll(List.of(
                 new NotificationSectioner("PromotionsSection", 0, (record) ->
                     NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())),
                 new NotificationSectioner("SocialSection", 0, (record) ->
@@ -131,18 +132,36 @@
                 new NotificationSectioner("NewsSection", 0, (record) ->
                     NotificationChannel.NEWS_ID.equals(record.getChannel().getId())),
                 new NotificationSectioner("RecsSection", 0, (record) ->
-                    NotificationChannel.RECS_ID.equals(record.getChannel().getId())),
-                new NotificationSectioner("AlertingSection", 0, (record) ->
-                    record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
-                new NotificationSectioner("SilentSection", 1, (record) ->
-                    record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
-        } else {
-            return List.of(
-                new NotificationSectioner("AlertingSection", 0, (record) ->
-                    record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
-                new NotificationSectioner("SilentSection", 1, (record) ->
-                    record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+                    NotificationChannel.RECS_ID.equals(record.getChannel().getId()))));
         }
+
+        if (Flags.notificationForceGroupConversations()) {
+            // add priority people section
+            sectionsList.add(new NotificationSectioner("PeopleSection(priority)", 1, (record) ->
+                    record.isConversation() && record.getChannel().isImportantConversation()));
+
+            if (android.app.Flags.sortSectionByTime()) {
+                // add single people (alerting) section
+                sectionsList.add(new NotificationSectioner("PeopleSection", 0,
+                        NotificationRecord::isConversation));
+            } else {
+                // add people alerting section
+                sectionsList.add(new NotificationSectioner("PeopleSection(alerting)", 1, (record) ->
+                        record.isConversation()
+                        && record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT));
+                // add people silent section
+                sectionsList.add(new NotificationSectioner("PeopleSection(silent)", 1, (record) ->
+                        record.isConversation()
+                        && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+            }
+        }
+
+        sectionsList.addAll(List.of(
+            new NotificationSectioner("AlertingSection", 0, (record) ->
+                record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+            new NotificationSectioner("SilentSection", 1, (record) ->
+                record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT)));
+        return sectionsList;
     }
 
     public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
@@ -1387,8 +1406,10 @@
         }
 
         private boolean isNotificationGroupable(final NotificationRecord record) {
-            if (record.isConversation()) {
-                return false;
+            if (!Flags.notificationForceGroupConversations()) {
+                if (record.isConversation()) {
+                    return false;
+                }
             }
 
             Notification notification = record.getSbn().getNotification();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ffb2bb6..dbe778e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1583,6 +1583,8 @@
                     // respond to direct replies with updates. So we need to update System UI
                     // immediately.
                     if (lifetimeExtensionRefactor()) {
+                        // We need to reset this to allow the notif to be updated again.
+                        r.setCanceledAfterLifetimeExtension(false);
                         maybeNotifySystemUiListenerLifetimeExtendedLocked(r,
                                 r.getSbn().getPackageName(), packageImportance);
                     }
@@ -1639,9 +1641,12 @@
                     // respond to direct replies with updates. So we need to update System UI
                     // immediately.
                     if (lifetimeExtensionRefactor()) {
+                        // We need to reset this to allow the notif to be updated again.
+                        r.setCanceledAfterLifetimeExtension(false);
                         maybeNotifySystemUiListenerLifetimeExtendedLocked(r,
                                 r.getSbn().getPackageName(), packageImportance);
                     }
+
                     r.recordSmartReplied();
                     LogMaker logMaker = r.getLogMaker()
                             .setCategory(MetricsEvent.SMART_REPLY_ACTION)
@@ -11741,17 +11746,39 @@
     private void maybeNotifySystemUiListenerLifetimeExtendedLocked(NotificationRecord record,
             String pkg, int packageImportance) {
         if (record != null && (record.getSbn().getNotification().flags
-                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
+                && !record.isCanceledAfterLifetimeExtension()) {
             boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
 
-            // Lifetime extended notifications don't need to alert on state change.
+            // Save the original Record's post silently value, so we can restore it after we send
+            // the SystemUI specific silent update.
+            boolean savedPostSilentlyState = record.shouldPostSilently();
+            boolean savedOnlyAlertOnceState = (record.getNotification().flags
+                    & FLAG_ONLY_ALERT_ONCE) > 0;
+            // Lifetime extended notifications don't need to alert on new state change.
             record.setPostSilently(true);
             // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
             record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
 
+            PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker(null);
+            tracker.addCleanupRunnable(() -> {
+                synchronized (mNotificationLock) {
+                    // Mark that the notification has been updated due to cancelation, so it won't
+                    // be updated again if the app cancels multiple times.
+                    record.setCanceledAfterLifetimeExtension(true);
+                    // Set the post silently status to the record's previous value.
+                    record.setPostSilently(savedPostSilentlyState);
+                    // Remove FLAG_ONLY_ALERT_ONCE if the notification did not previously have it.
+                    if (!savedOnlyAlertOnceState) {
+                        record.getNotification().flags &= ~FLAG_ONLY_ALERT_ONCE;
+                    }
+                }
+            });
+
             mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
-                    record, isAppForeground, /* isAppProvided= */ false,
-                    mPostNotificationTrackerFactory.newTracker(null)));
+                    record, isAppForeground, /* isAppProvided= */ false, tracker));
+
+            EventLogTags.writeNotificationCancelPrevented(record.getKey());
         }
     }
 
@@ -13351,17 +13378,23 @@
         @ElapsedRealtimeLong private final long mStartTime;
         @Nullable private final WakeLock mWakeLock;
         private boolean mOngoing;
+        private final List<Runnable> mCleanupRunnables;
 
         @VisibleForTesting
         PostNotificationTracker(@Nullable WakeLock wakeLock) {
             mStartTime = SystemClock.elapsedRealtime();
             mWakeLock = wakeLock;
             mOngoing = true;
+            mCleanupRunnables = new ArrayList<Runnable>();
             if (DBG) {
                 Slog.d(TAG, "PostNotification: Started");
             }
         }
 
+        void addCleanupRunnable(Runnable runnable) {
+            mCleanupRunnables.add(runnable);
+        }
+
         @ElapsedRealtimeLong
         long getStartTime() {
             return mStartTime;
@@ -13373,8 +13406,9 @@
         }
 
         /**
-         * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
-         * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+         * Cancels the tracker (releasing the acquired WakeLock) and runs any set cleanup runnables.
+         * Either {@link #finish} or {@link #cancel} (exclusively) should be called on this object
+         * before it's discarded.
          */
         void cancel() {
             if (!isOngoing()) {
@@ -13385,6 +13419,9 @@
             if (mWakeLock != null) {
                 Binder.withCleanCallingIdentity(() -> mWakeLock.release());
             }
+            for (Runnable r : mCleanupRunnables) {
+                r.run();
+            }
             if (DBG) {
                 long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
                 Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -13393,9 +13430,10 @@
         }
 
         /**
-         * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
-         * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
-         * (exclusively) should be called on this object before it's discarded.
+         * Finishes the tracker (releasing the acquired WakeLock), runs any set cleanup runnables,
+         * and returns the time elapsed since the operation started, in milliseconds.
+         * Either {@link #finish} or {@link #cancel} (exclusively) should be called on this object
+         * before it's discarded.
          */
         @DurationMillisLong
         long finish() {
@@ -13408,6 +13446,9 @@
             if (mWakeLock != null) {
                 Binder.withCleanCallingIdentity(() -> mWakeLock.release());
             }
+            for (Runnable r : mCleanupRunnables) {
+                r.run();
+            }
             if (DBG) {
                 Slog.d(TAG,
                         TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1392003..e541246 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -222,6 +222,9 @@
     private boolean mPendingLogUpdate = false;
     private int mProposedImportance = IMPORTANCE_UNSPECIFIED;
     private boolean mSensitiveContent = false;
+    // Whether an app has attempted to cancel this notification after it has been marked as
+    // lifetime extended.
+    private boolean mCanceledAfterLifetimeExtension = false;
 
     public NotificationRecord(Context context, StatusBarNotification sbn,
             NotificationChannel channel) {
@@ -535,6 +538,7 @@
                 + NotificationListenerService.Ranking.importanceToString(mProposedImportance));
         pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
         pw.println(prefix + "mSensitiveContent=" + mSensitiveContent);
+        pw.println(prefix + "mCanceledAfterLifetimeExtension=" + mCanceledAfterLifetimeExtension);
         pw.println(prefix + "mIntercept=" + mIntercept);
         pw.println(prefix + "mHidden==" + mHidden);
         pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
@@ -1620,6 +1624,14 @@
         mPkgAllowedAsConvo = allowedAsConvo;
     }
 
+    public boolean isCanceledAfterLifetimeExtension() {
+        return mCanceledAfterLifetimeExtension;
+    }
+
+    public void setCanceledAfterLifetimeExtension(boolean canceledAfterLifetimeExtension) {
+        mCanceledAfterLifetimeExtension = canceledAfterLifetimeExtension;
+    }
+
     /**
      * Whether this notification is a conversation notification.
      */
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 06d1285..6ff0e04 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -765,7 +765,9 @@
             try {
                 ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
                 rule.name = applicationInfo.loadLabel(mPm).toString();
-                rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+                if (!Flags.modesUi()) {
+                    rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
+                }
             } catch (PackageManager.NameNotFoundException e) {
                 // Should not happen, since it's the app calling us (?)
                 Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -1750,6 +1752,15 @@
                                     manualRulePolicy.overwrittenWith(automaticRule.zenPolicy);
                         }
                     }
+
+                    if (Flags.modesApi() && Flags.modesUi()
+                            && config.version < ZenModeConfig.XML_VERSION_MODES_UI) {
+                        // Clear icons from implicit rules. App icons are not suitable for some
+                        // surfaces, so juse use a default (the user can select a different one).
+                        if (ZenModeConfig.isImplicitRuleId(automaticRule.id)) {
+                            automaticRule.iconResName = null;
+                        }
+                    }
                 }
             }
 
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 7265cff..aac2c40 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -149,3 +149,10 @@
   description: "This flag enables forced auto-grouping singleton groups"
   bug: "336488844"
 }
+
+flag {
+  name: "notification_force_group_conversations"
+  namespace: "systemui"
+  description: "This flag enables forced auto-grouping conversations"
+  bug: "336488844"
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index e3061a7..4135161 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -69,6 +69,7 @@
         mUserManager = mSystemUserContext.getSystemService(UserManager.class);
         NotificationChannel channel = new NotificationChannel(BUSN_CHANNEL_ID, BUSN_CHANNEL_NAME,
                 NotificationManager.IMPORTANCE_HIGH);
+        channel.setSound(null, null);
         mNotificationManager.createNotificationChannel(channel);
         setupFocusControlAudioPolicy();
     }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 95e5b84..2bc6d53 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -503,26 +503,21 @@
             String restriction,
             boolean isMainUser,
             boolean isProfileOwnerOnOrgOwnedDevice) {
-        if (android.app.admin.flags.Flags.esimManagementEnabled()) {
-            if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
-                return false;
-            }
-            if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
-                return false;
-            }
-            if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
-                return false;
-            }
-            if (!isProfileOwnerOnOrgOwnedDevice
-                    && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
-                            restriction)) {
-                return false;
-            }
-            return true;
+        if (IMMUTABLE_BY_OWNERS.contains(restriction)) {
+            return false;
         }
-        return !IMMUTABLE_BY_OWNERS.contains(restriction)
-                && !DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)
-                && !(!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction));
+        if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)) {
+            return false;
+        }
+        if (!isMainUser && MAIN_USER_ONLY_RESTRICTIONS.contains(restriction)) {
+            return false;
+        }
+        if (!isProfileOwnerOnOrgOwnedDevice
+                && PROFILE_OWNER_ORGANIZATION_OWNED_PROFILE_RESTRICTIONS.contains(
+                restriction)) {
+            return false;
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 24323c8..8a3c74b 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -1,24 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "CtsPermissionTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.SplitPermissionTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.PermissionFlagsTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
-                }
-            ]
+            "name": "CtsPermissionTestCases_Platform"
         },
         {
             "name": "CtsAppSecurityHostTestCases",
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 338b479..bdb174d 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -46,18 +46,7 @@
       ]
     },
     {
-      "name": "CtsPermissionTestCases",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "include-filter": "android.permission.cts.SplitPermissionTest"
-        },
-        {
-          "include-filter": "android.permission.cts.BackgroundPermissionsTest"
-        }
-      ]
+      "name": "CtsPermissionTestCases_Platform"
     },
     {
       "name": "CtsBackupTestCases",
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index d0b70c3..da8b01a 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -176,8 +176,9 @@
 
         final DreamManagerInternal dreamManager =
                 LocalServices.getService(DreamManagerInternal.class);
-
-        dreamManager.registerDreamManagerStateListener(mDreamManagerStateListener);
+        if(dreamManager != null){
+            dreamManager.registerDreamManagerStateListener(mDreamManagerStateListener);
+        }
     }
 
     private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a27360d..12e7fd0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1379,8 +1379,10 @@
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
 
-            // This DreamManager method does not acquire a lock, so it should be safe to call.
-            mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+            if(mDreamManager != null){
+                // This DreamManager method does not acquire a lock, so it should be safe to call.
+                mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+            }
 
             mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
                     mInjector.createSuspendBlocker(
@@ -3543,7 +3545,7 @@
         }
 
         // Stop dream.
-        if (isDreaming) {
+        if (isDreaming && mDreamManager != null) {
             mDreamManager.stopDream(/* immediate= */ false, "power manager request" /*reason*/);
         }
     }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 7f24769..822ec2e 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -1644,8 +1644,7 @@
                         if (Flags.allowThermalHeadroomThresholds()) {
                             for (int severity = ThrottlingSeverity.LIGHT;
                                     severity <= ThrottlingSeverity.SHUTDOWN; severity++) {
-                                if (severity != ThrottlingSeverity.SEVERE
-                                        && threshold.hotThrottlingThresholds.length > severity) {
+                                if (threshold.hotThrottlingThresholds.length > severity) {
                                     updateHeadroomThreshold(severity,
                                             threshold.hotThrottlingThresholds[severity],
                                             severeThreshold);
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index 9a4c60d..68760aa 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -864,7 +864,8 @@
                     buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
                             R.string.dynamic_mode_notification_title,
                             R.string.dynamic_mode_notification_summary,
-                            Settings.ACTION_BATTERY_SAVER_SETTINGS, 0L),
+                            Settings.ACTION_BATTERY_SAVER_SETTINGS, 0L,
+                            R.drawable.ic_settings),
                     UserHandle.ALL);
         });
     }
@@ -889,7 +890,8 @@
                             R.string.dynamic_mode_notification_summary_v2,
                             Settings.ACTION_BATTERY_SAVER_SETTINGS,
                             0L /* timeoutMs */,
-                            highlightBundle),
+                            highlightBundle,
+                            R.drawable.ic_qs_battery_saver),
                     UserHandle.ALL);
         });
     }
@@ -911,7 +913,8 @@
                             R.string.battery_saver_off_notification_title,
                             R.string.battery_saver_charged_notification_summary,
                             Settings.ACTION_BATTERY_SAVER_SETTINGS,
-                            STICKY_DISABLED_NOTIFY_TIMEOUT_MS),
+                            STICKY_DISABLED_NOTIFY_TIMEOUT_MS,
+                            R.drawable.ic_settings),
                     UserHandle.ALL);
         });
     }
@@ -926,7 +929,7 @@
     }
 
     private Notification buildNotification(@NonNull String channelId, @StringRes int titleId,
-            @StringRes int summaryId, @NonNull String intentAction, long timeoutMs) {
+            @StringRes int summaryId, @NonNull String intentAction, long timeoutMs, int iconResId) {
         Resources res = mContext.getResources();
         Intent intent = new Intent(intentAction);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -937,7 +940,7 @@
         final String summary = res.getString(summaryId);
 
         return new Notification.Builder(mContext, channelId)
-                .setSmallIcon(R.drawable.ic_battery)
+                .setSmallIcon(iconResId)
                 .setContentTitle(title)
                 .setContentText(summary)
                 .setContentIntent(batterySaverIntent)
@@ -950,7 +953,7 @@
 
     private Notification buildNotificationV2(@NonNull String channelId, @StringRes int titleId,
             @StringRes int summaryId, @NonNull String intentAction, long timeoutMs,
-            @NonNull Bundle highlightBundle) {
+            @NonNull Bundle highlightBundle, int iconResId) {
         Resources res = mContext.getResources();
         Intent intent = new Intent(intentAction)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -963,7 +966,7 @@
         final String summary = res.getString(summaryId);
 
         return new Notification.Builder(mContext, channelId)
-                .setSmallIcon(R.drawable.ic_battery)
+                .setSmallIcon(iconResId)
                 .setContentTitle(title)
                 .setContentText(summary)
                 .setContentIntent(batterySaverIntent)
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 46e779f..8311034 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -120,7 +120,7 @@
     private int mScreenState;
 
     @GuardedBy("this")
-    private int[] mPerDisplayScreenStates = null;
+    private int[] mPerDisplayScreenStates;
 
     @GuardedBy("this")
     private boolean mUseLatestStates = true;
@@ -243,6 +243,7 @@
             }
             synchronized (mStats) {
                 mStats.initEnergyConsumerStatsLocked(supportedStdBuckets, customBucketNames);
+                mPerDisplayScreenStates = new int[mStats.getDisplayCount()];
             }
         }
     }
@@ -490,6 +491,12 @@
                                 onBatteryScreenOff, screenState, displayScreenStates,
                                 useLatestStates);
                     } finally {
+                        if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
+                            synchronized (mStats) {
+                                // This helps mStats deal with ignoring data from prior to resets.
+                                mStats.informThatAllExternalStatsAreFlushed();
+                            }
+                        }
                         if (DEBUG) {
                             Slog.d(TAG, "end updateExternalStatsSync");
                         }
@@ -767,7 +774,6 @@
 
         // WiFi and Modem state are updated without the mStats lock held, because they
         // do some network stats retrieval before internally grabbing the mStats lock.
-
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
                 final long wifiChargeUC =
@@ -790,11 +796,6 @@
             mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime,
                     uptime, networkStatsManager);
         }
-
-        if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
-            // This helps mStats deal with ignoring data from prior to resets.
-            mStats.informThatAllExternalStatsAreFlushed();
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d04b733..385561d 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1167,7 +1167,6 @@
     private static final int USB_DATA_CONNECTED = 2;
     int mUsbDataState = USB_DATA_UNKNOWN;
 
-    private static final int GPS_SIGNAL_QUALITY_NONE = 2;
     int mGpsSignalQualityBin = -1;
     final StopwatchTimer[] mGpsSignalQualityTimer =
         new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
@@ -5528,7 +5527,7 @@
             mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
                     HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
             mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
-                    GPS_SIGNAL_QUALITY_NONE);
+                    HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
             stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
             mGpsSignalQualityBin = -1;
             if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
@@ -15301,15 +15300,6 @@
                 mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
             }
         }
-        if (!onBattery &&
-                (status == BatteryManager.BATTERY_STATUS_FULL ||
-                        status == BatteryManager.BATTERY_STATUS_UNKNOWN)) {
-            // We don't record history while we are plugged in and fully charged
-            // (or when battery is not present).  The next time we are
-            // unplugged, history will be cleared.
-            mHistory.setHistoryRecordingEnabled(DEBUG);
-        }
-
         mLastLearnedBatteryCapacityUah = chargeFullUah;
         if (mMinLearnedBatteryCapacityUah == -1) {
             mMinLearnedBatteryCapacityUah = chargeFullUah;
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
index 393fa39..03df46a 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -115,6 +115,12 @@
                 mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
             }
         } else {
+            if (mInitiatingUid == Process.INVALID_UID) {
+                if (item.eventCode == (BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                        | BatteryStats.HistoryItem.EVENT_FLAG_FINISH)) {
+                    mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
+                }
+            }
             recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
             mInitiatingUid = Process.INVALID_UID;
             if (!mEnergyConsumerSupported) {
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
index 572bde9..0b28710 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
@@ -27,15 +27,15 @@
 import java.util.Arrays;
 
 public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
-    private int mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
-    private long mGnssSignalLevelTimestamp;
-    private final long[] mGnssSignalDurations =
-            new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
     private static final GnssPowerStatsLayout sStatsLayout = new GnssPowerStatsLayout();
     private final UsageBasedPowerEstimator[] mSignalLevelEstimators =
             new UsageBasedPowerEstimator[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
     private final boolean mUseSignalLevelEstimators;
     private long[] mTmpDeviceStatsArray;
+    private int mGnssSignalLevel;
+    private long mGnssSignalLevelTimestamp;
+    private final long[] mGnssSignalDurations =
+            new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
 
     public GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
@@ -55,20 +55,33 @@
     }
 
     @Override
-    protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
-        if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) == 0) {
-            mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
-            return STATE_OFF;
-        }
+    void start(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+        super.start(stats, timestampMs);
 
-        noteGnssSignalLevel(item);
-        return STATE_ON;
+        mGnssSignalLevelTimestamp = timestampMs;
+        mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+        Arrays.fill(mGnssSignalDurations, 0);
     }
 
-    private void noteGnssSignalLevel(BatteryStats.HistoryItem item) {
-        int signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
-                >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
-        if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+    @Override
+    protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+        return (item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0
+                ? STATE_ON : STATE_OFF;
+    }
+
+    @Override
+    void noteStateChange(PowerComponentAggregatedPowerStats stats, BatteryStats.HistoryItem item) {
+        super.noteStateChange(stats, item);
+
+        int signalLevel;
+        if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) != 0) {
+            signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+                    >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+            if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+                // Default GNSS signal quality to GOOD for the purposes of power attribution
+                signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD;
+            }
+        } else {
             signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
         }
         if (signalLevel == mGnssSignalLevel) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index d6bf02f..6466519 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -190,6 +190,9 @@
                 case "notification-icons":
                     info.setNotificationIconsDisabled(true);
                     break;
+                case "quick-settings":
+                    info.setQuickSettingsDisabled(true);
+                    break;
                 default:
                     break;
             }
@@ -277,6 +280,7 @@
         pw.println("        system-icons        - disable system icons appearing in status bar");
         pw.println("        clock               - disable clock appearing in status bar");
         pw.println("        notification-icons  - disable notification icons from status bar");
+        pw.println("        quick-settings      - disable Quick Settings");
         pw.println("");
         pw.println("  tracing (start | stop)");
         pw.println("    Start or stop SystemUI tracing");
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
new file mode 100644
index 0000000..b4d3862
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.os.vibrator.Flags;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * A vibration session holding a single {@link ExternalVibration} request.
+ */
+final class ExternalVibrationSession extends Vibration
+        implements VibrationSession, IBinder.DeathRecipient {
+
+    private final ExternalVibration mExternalVibration;
+    private final ExternalVibrationScale mScale = new ExternalVibrationScale();
+
+    @Nullable
+    private Runnable mBinderDeathCallback;
+
+    ExternalVibrationSession(ExternalVibration externalVibration) {
+        super(externalVibration.getToken(), new CallerInfo(
+                externalVibration.getVibrationAttributes(), externalVibration.getUid(),
+                // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
+                // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
+                Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
+        mExternalVibration = externalVibration;
+    }
+
+    public ExternalVibrationScale getScale() {
+        return mScale;
+    }
+
+    @Override
+    public CallerInfo getCallerInfo() {
+        return callerInfo;
+    }
+
+    @Override
+    public VibrationSession.DebugInfo getDebugInfo() {
+        return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
+                /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
+                callerInfo);
+    }
+
+    @Override
+    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+        return new VibrationStats.StatsInfo(
+                mExternalVibration.getUid(),
+                FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
+                completionUptimeMillis);
+    }
+
+    @Override
+    public boolean isRepeating() {
+        // We don't currently know if the external vibration is repeating, so we just use a
+        // heuristic based on the usage. Ideally this would be propagated in the ExternalVibration.
+        int usage = mExternalVibration.getVibrationAttributes().getUsage();
+        return usage == VibrationAttributes.USAGE_RINGTONE
+                || usage == VibrationAttributes.USAGE_ALARM;
+    }
+
+    @Override
+    public void linkToDeath(Runnable callback) {
+        synchronized (this) {
+            mBinderDeathCallback = callback;
+        }
+        mExternalVibration.linkToDeath(this);
+    }
+
+    @Override
+    public void unlinkToDeath() {
+        mExternalVibration.unlinkToDeath(this);
+        synchronized (this) {
+            mBinderDeathCallback = null;
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        synchronized (this) {
+            if (mBinderDeathCallback != null) {
+                mBinderDeathCallback.run();
+            }
+        }
+    }
+
+    @Override
+    void end(EndInfo endInfo) {
+        super.end(endInfo);
+        if (stats.hasStarted()) {
+            // External vibration doesn't have feedback from total time the vibrator was playing
+            // with non-zero amplitude, so we use the duration between start and end times of
+            // the vibration as the time the vibrator was ON, since the haptic channels are
+            // open for this duration and can receive vibration waveform data.
+            stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+        }
+    }
+
+    @Override
+    public void notifyEnded() {
+        // Notify external client that this vibration should stop sending data to the vibrator.
+        mExternalVibration.mute();
+    }
+
+    boolean isHoldingSameVibration(ExternalVibration vib) {
+        return mExternalVibration.equals(vib);
+    }
+
+    void muteScale() {
+        mScale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+        if (Flags.hapticsScaleV2Enabled()) {
+            mScale.scaleFactor = 0;
+        }
+    }
+
+    void scale(VibrationScaler scaler, int usage) {
+        mScale.scaleLevel = scaler.getScaleLevel(usage);
+        if (Flags.hapticsScaleV2Enabled()) {
+            mScale.scaleFactor = scaler.getScaleFactor(usage);
+        }
+        mScale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
+        stats.reportAdaptiveScale(mScale.adaptiveHapticsScale);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index fe0cf59..ea4bd01 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -51,37 +51,22 @@
     @NonNull
     private volatile CombinedVibration mEffectToPlay;
 
-    /** Vibration status. */
-    private Vibration.Status mStatus;
-
     /** Reported scale values applied to the vibration effects. */
     private int mScaleLevel;
     private float mAdaptiveScale;
 
     HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
-            @NonNull CallerInfo callerInfo) {
+            @NonNull VibrationSession.CallerInfo callerInfo) {
         super(token, callerInfo);
         mOriginalEffect = effect;
         mEffectToPlay = effect;
-        mStatus = Vibration.Status.RUNNING;
         mScaleLevel = VibrationScaler.SCALE_NONE;
         mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
     }
 
-    /**
-     * Set the {@link Status} of this vibration and reports the current system time as this
-     * vibration end time, for debugging purposes.
-     *
-     * <p>This method will only accept given value if the current status is {@link
-     * Status#RUNNING}.
-     */
-    public void end(EndInfo info) {
-        if (hasEnded()) {
-            // Vibration already ended, keep first ending status set and ignore this one.
-            return;
-        }
-        mStatus = info.status;
-        stats.reportEnded(info.endedBy);
+    @Override
+    public void end(EndInfo endInfo) {
+        super.end(endInfo);
         mCompletionLatch.countDown();
     }
 
@@ -144,11 +129,6 @@
         // No need to update fallback effects, they are already configured per device.
     }
 
-    /** Return true is current status is different from {@link Status#RUNNING}. */
-    public boolean hasEnded() {
-        return mStatus != Status.RUNNING;
-    }
-
     @Override
     public boolean isRepeating() {
         return mOriginalEffect.getDuration() == Long.MAX_VALUE;
@@ -159,16 +139,16 @@
         return mEffectToPlay;
     }
 
-    /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
-    public Vibration.DebugInfo getDebugInfo() {
+    @Override
+    public VibrationSession.DebugInfo getDebugInfo() {
         // Clear the original effect if it's the same as the effect that was played, for simplicity
         CombinedVibration originalEffect =
                 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
-        return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
+        return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
                 mScaleLevel, mAdaptiveScale, callerInfo);
     }
 
-    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+    @Override
     public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
         int vibrationType = mEffectToPlay.hasVendorEffects()
                 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
@@ -176,7 +156,7 @@
                         ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                         : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
         return new VibrationStats.StatsInfo(
-                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
+                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
                 stats, completionUptimeMillis);
     }
 
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 65fc7b2..d10ef31 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -28,7 +29,10 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.InputDevice;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.vibrator.persistence.XmlParserException;
 import com.android.internal.vibrator.persistence.XmlReader;
@@ -42,6 +46,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.Reader;
+import java.util.Locale;
 
 /**
  * Class that loads custom {@link VibrationEffect} to be performed for each
@@ -92,105 +97,146 @@
     private static final String ATTRIBUTE_ID = "id";
 
     /**
-     * Parses the haptic feedback vibration customization XML file for the device, and provides a
-     * mapping of the customized effect IDs to their respective {@link VibrationEffect}s.
-     *
-     * <p>This is potentially expensive, so avoid calling repeatedly. One call is enough, and the
-     * caller should process the returned mapping (if any) for further queries.
-     *
-     * @param res {@link Resources} object to be used for reading the device's resources.
-     * @return a {@link SparseArray} that maps each customized haptic feedback effect ID to its
-     *      respective {@link VibrationEffect}, or {@code null}, if the device has not configured
-     *      a file for haptic feedback constants customization.
-     * @throws {@link IOException} if an IO error occurs while parsing the customization XML.
-     * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing
-     *      the XML, like an invalid XML content or an invalid haptic feedback constant.
+     * A {@link SparseArray} that maps each customized haptic feedback effect ID to its
+     * respective {@link VibrationEffect}. If this is empty, system's default vibration will be
+     * used.
      */
-    @Nullable
-    static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
-            throws CustomizationParserException, IOException {
-        try {
-            return loadVibrationsInternal(res, vibratorInfo);
-        } catch (VibrationXmlParser.ParseFailedException
-                 | XmlParserException
-                 | XmlPullParserException e) {
-            throw new CustomizationParserException(
-                    "Error parsing haptic feedback customization file.", e);
+    @NonNull
+    private final SparseArray<VibrationEffect> mHapticCustomizations;
+
+    /**
+     * A {@link SparseArray} similar to {@link mHapticCustomizations} but for rotary input source
+     * specific customization.
+     */
+    @NonNull
+    private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceRotary;
+
+    /**
+     * A {@link SparseArray} similar to {@link mHapticCustomizations} but for touch screen input
+     * source specific customization.
+     */
+    @NonNull
+    private final SparseArray<VibrationEffect> mHapticCustomizationsForSourceTouchScreen;
+
+    HapticFeedbackCustomization(Resources res, VibratorInfo vibratorInfo) {
+        if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+            Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+            mHapticCustomizations = new SparseArray<>();
+            mHapticCustomizationsForSourceRotary = new SparseArray<>();
+            mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
+            return;
+        }
+
+        // Load base customizations.
+        SparseArray<VibrationEffect> hapticCustomizations;
+        hapticCustomizations = loadCustomizedFeedbackVibrationFromFile(res, vibratorInfo);
+        if (hapticCustomizations.size() == 0) {
+            // Input source customized haptic feedback was directly added in res. So, no need to old
+            // loading path.
+            hapticCustomizations = loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+                    R.xml.haptic_feedback_customization);
+        }
+        mHapticCustomizations = hapticCustomizations;
+
+        // Load customizations specified by input sources.
+        if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+            mHapticCustomizationsForSourceRotary =
+                    loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+                            R.xml.haptic_feedback_customization_source_rotary_encoder);
+            mHapticCustomizationsForSourceTouchScreen =
+                    loadCustomizedFeedbackVibrationFromRes(res, vibratorInfo,
+                            R.xml.haptic_feedback_customization_source_touchscreen);
+        } else {
+            mHapticCustomizationsForSourceRotary = new SparseArray<>();
+            mHapticCustomizationsForSourceTouchScreen = new SparseArray<>();
         }
     }
 
+    @VisibleForTesting
+    HapticFeedbackCustomization(@NonNull SparseArray<VibrationEffect> hapticCustomizations,
+            @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceRotary,
+            @NonNull SparseArray<VibrationEffect> hapticCustomizationsForSourceTouchScreen) {
+        mHapticCustomizations = hapticCustomizations;
+        mHapticCustomizationsForSourceRotary = hapticCustomizationsForSourceRotary;
+        mHapticCustomizationsForSourceTouchScreen = hapticCustomizationsForSourceTouchScreen;
+    }
+
     @Nullable
-    private static SparseArray<VibrationEffect> loadVibrationsInternal(
-            Resources res, VibratorInfo vibratorInfo) throws
-                    CustomizationParserException,
-                    IOException,
-                    XmlParserException,
-                    XmlPullParserException {
-        if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
-            Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
-            return null;
+    VibrationEffect getEffect(int effectId) {
+        return mHapticCustomizations.get(effectId);
+    }
+
+    @Nullable
+    VibrationEffect getEffect(int effectId, int inputSource) {
+        VibrationEffect resultVibration = null;
+        if ((InputDevice.SOURCE_ROTARY_ENCODER & inputSource) != 0) {
+            resultVibration = mHapticCustomizationsForSourceRotary.get(effectId);
+        } else if ((InputDevice.SOURCE_TOUCHSCREEN & inputSource) != 0) {
+            resultVibration = mHapticCustomizationsForSourceTouchScreen.get(effectId);
         }
-
-        // Old loading path that reads customization from file at dir defined by config.
-        TypedXmlPullParser parser = readCustomizationFile(res);
-        if (parser == null) {
-            // When old loading path doesn't succeed, try loading customization from resources.
-            parser = readCustomizationResources(res);
+        if (resultVibration == null) {
+            resultVibration = mHapticCustomizations.get(effectId);
         }
-        if (parser == null) {
-            Slog.d(TAG, "No loadable haptic feedback customization.");
-            return null;
-        }
+        return resultVibration;
+    }
 
-        XmlUtils.beginDocument(parser, TAG_CONSTANTS);
-        XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
-        int rootDepth = parser.getDepth();
-
-        SparseArray<VibrationEffect> mapping = new SparseArray<>();
-        while (XmlReader.readNextTagWithin(parser, rootDepth)) {
-            XmlValidator.checkStartTag(parser, TAG_CONSTANT);
-            int customizationDepth = parser.getDepth();
-
-            // Only attribute in tag is the `id` attribute.
-            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
-            int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
-            if (mapping.contains(effectId)) {
-                throw new CustomizationParserException(
-                        "Multiple customizations found for effect " + effectId);
+    /**
+     * Parses the haptic feedback vibration customization XML file for the device whose directory is
+     * specified by config. See {@link R.string.config_hapticFeedbackCustomizationFile}.
+     *
+     * @return Return a mapping of the customized effect IDs to their respective
+     * {@link VibrationEffect}s.
+     */
+    @NonNull
+    private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromFile(
+            Resources res, VibratorInfo vibratorInfo) {
+        try {
+            TypedXmlPullParser parser = readCustomizationFile(res);
+            if (parser == null) {
+                Slog.d(TAG, "No loadable haptic feedback customization from file.");
+                return new SparseArray<>();
             }
-
-            // Move the parser one step into the `<constant>` tag.
-            XmlValidator.checkParserCondition(
-                    XmlReader.readNextTagWithin(parser, customizationDepth),
-                    "Unsupported empty customization tag for effect " + effectId);
-
-            ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
-                    parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
-            VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
-            if (effect != null) {
-                if (effect.getDuration() == Long.MAX_VALUE) {
-                    throw new CustomizationParserException(String.format(
-                            "Vibration for effect ID %d is repeating, which is not allowed as a"
-                            + " haptic feedback: %s", effectId, effect));
-                }
-                mapping.put(effectId, effect);
-            }
-
-            XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
+            return parseVibrations(parser, vibratorInfo);
+        } catch (XmlPullParserException | XmlParserException | IOException e) {
+            Slog.e(TAG, "Error parsing haptic feedback customizations from file", e);
+            return new SparseArray<>();
         }
+    }
 
-        // Make checks that the XML ends well.
-        XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
-        XmlReader.readDocumentEndTag(parser);
-
-        return mapping;
+    /**
+     * Parses the haptic feedback vibration customization XML resource for the device.
+     *
+     * @return Return a mapping of the customized effect IDs to their respective
+     * {@link VibrationEffect}s.
+     */
+    @NonNull
+    private static SparseArray<VibrationEffect> loadCustomizedFeedbackVibrationFromRes(
+            Resources res, VibratorInfo vibratorInfo, int xmlResId) {
+        try {
+            TypedXmlPullParser parser = readCustomizationResources(res, xmlResId);
+            if (parser == null) {
+                Slog.d(TAG, "No loadable haptic feedback customization from res.");
+                return new SparseArray<>();
+            }
+            return parseVibrations(parser, vibratorInfo);
+        } catch (XmlPullParserException | XmlParserException | IOException e) {
+            Slog.e(TAG, "Error parsing haptic feedback customizations from res", e);
+            return new SparseArray<>();
+        }
     }
 
     // TODO(b/356412421): deprecate old path related files.
     private static TypedXmlPullParser readCustomizationFile(Resources res)
             throws XmlPullParserException {
-        String customizationFile = res.getString(
-                com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+        String customizationFile;
+        try {
+            customizationFile = res.getString(
+                    R.string.config_hapticFeedbackCustomizationFile);
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "Customization file directory config not found.", e);
+            return null;
+        }
+
         if (TextUtils.isEmpty(customizationFile)) {
             return null;
         }
@@ -211,13 +257,14 @@
         return parser;
     }
 
-    private static TypedXmlPullParser readCustomizationResources(Resources res) {
+    @Nullable
+    private static TypedXmlPullParser readCustomizationResources(Resources res, int xmlResId) {
         if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
             return null;
         }
         final XmlResourceParser resParser;
         try {
-            resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+            resParser = res.getXml(xmlResId);
         } catch (Resources.NotFoundException e) {
             Slog.e(TAG, "Haptic customization resource not found.", e);
             return null;
@@ -226,16 +273,52 @@
         return XmlUtils.makeTyped(resParser);
     }
 
-    /**
-     * Represents an error while parsing a haptic feedback customization XML.
-     */
-    static final class CustomizationParserException extends Exception {
-        private CustomizationParserException(String message) {
-            super(message);
+    @NonNull
+    private static SparseArray<VibrationEffect> parseVibrations(TypedXmlPullParser parser,
+            VibratorInfo vibratorInfo)
+            throws XmlPullParserException, IOException, XmlParserException {
+        XmlUtils.beginDocument(parser, TAG_CONSTANTS);
+        XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+        int rootDepth = parser.getDepth();
+
+        SparseArray<VibrationEffect> mapping = new SparseArray<>();
+        while (XmlReader.readNextTagWithin(parser, rootDepth)) {
+            XmlValidator.checkStartTag(parser, TAG_CONSTANT);
+            int customizationDepth = parser.getDepth();
+
+            // Only attribute in tag is the `id` attribute.
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_ID);
+            int effectId = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_ID);
+            if (mapping.contains(effectId)) {
+                Slog.e(TAG, "Multiple customizations found for effect " + effectId);
+                return new SparseArray<>();
+            }
+
+            // Move the parser one step into the `<constant>` tag.
+            XmlValidator.checkParserCondition(
+                    XmlReader.readNextTagWithin(parser, customizationDepth),
+                    "Unsupported empty customization tag for effect " + effectId);
+
+            ParsedVibration parsedVibration = VibrationXmlParser.parseElement(
+                    parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+            VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
+            if (effect != null) {
+                if (effect.getDuration() == Long.MAX_VALUE) {
+                    Slog.e(TAG, String.format(Locale.getDefault(),
+                            "Vibration for effect ID %d is repeating, which is not allowed as a"
+                                    + " haptic feedback: %s", effectId, effect));
+                    return new SparseArray<>();
+                }
+                mapping.put(effectId, effect);
+            }
+
+            XmlReader.readEndTag(parser, TAG_CONSTANT, customizationDepth);
         }
 
-        private CustomizationParserException(String message, Throwable cause) {
-            super(message, cause);
-        }
+        // Make checks that the XML ends well.
+        XmlReader.readEndTag(parser, TAG_CONSTANTS, rootDepth);
+        XmlReader.readDocumentEndTag(parser);
+
+        return mapping;
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 0761087..cae6b34 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -16,19 +16,17 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.IOException;
 import java.io.PrintWriter;
 
 /**
@@ -52,39 +50,29 @@
     private final boolean mHapticTextHandleEnabled;
     // Vibrator effect for haptic feedback during boot when safe mode is enabled.
     private final VibrationEffect mSafeModeEnabledVibrationEffect;
-    // Haptic feedback vibration customizations specific to the device.
-    // If present and valid, a vibration here will be used for an effect.
-    // Otherwise, the system's default vibration will be used.
-    @Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
+
+    private final HapticFeedbackCustomization mHapticFeedbackCustomization;
 
     private float mKeyboardVibrationFixedAmplitude;
 
-    public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
-        this(res, vibrator.getInfo());
-    }
-
     public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
-        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
+        this(res, vibratorInfo, new HapticFeedbackCustomization(res, vibratorInfo));
     }
 
-    @VisibleForTesting HapticFeedbackVibrationProvider(
-            Resources res,
-            VibratorInfo vibratorInfo,
-            @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
+    @VisibleForTesting
+    HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo,
+            HapticFeedbackCustomization hapticFeedbackCustomization) {
         mVibratorInfo = vibratorInfo;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
+        mHapticFeedbackCustomization = hapticFeedbackCustomization;
 
-        if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
-            hapticCustomizations = null;
-        }
-        mHapticCustomizations = hapticCustomizations;
-        mSafeModeEnabledVibrationEffect =
-                effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
-                        ? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
-                        : VibrationSettings.createEffectFromResource(
-                                res,
-                                com.android.internal.R.array.config_safeModeEnabledVibePattern);
+        VibrationEffect safeModeVibration = mHapticFeedbackCustomization.getEffect(
+                HapticFeedbackConstants.SAFE_MODE_ENABLED);
+        mSafeModeEnabledVibrationEffect = safeModeVibration != null ? safeModeVibration
+                : VibrationSettings.createEffectFromResource(res,
+                        com.android.internal.R.array.config_safeModeEnabledVibePattern);
+
         mKeyboardVibrationFixedAmplitude = res.getFloat(
                 com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
         if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
@@ -100,89 +88,41 @@
      * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
      *          the provided effect ID is not supported.
      */
-    @Nullable public VibrationEffect getVibrationForHapticFeedback(int effectId) {
-        switch (effectId) {
-            case HapticFeedbackConstants.CONTEXT_CLICK:
-            case HapticFeedbackConstants.GESTURE_END:
-            case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
-            case HapticFeedbackConstants.SCROLL_TICK:
-            case HapticFeedbackConstants.SEGMENT_TICK:
-                return getVibration(effectId, VibrationEffect.EFFECT_TICK);
-
-            case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
-                if (!mHapticTextHandleEnabled) {
-                    return null;
-                }
-                // fallthrough
-            case HapticFeedbackConstants.CLOCK_TICK:
-            case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
-                return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
-
-            case HapticFeedbackConstants.KEYBOARD_RELEASE:
-            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
-                return getKeyboardVibration(effectId);
-
-            case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
-            case HapticFeedbackConstants.DRAG_CROSSING:
-                return getVibration(
-                        effectId,
-                        VibrationEffect.EFFECT_TICK,
-                        /* fallbackForPredefinedEffect= */ false);
-
-            case HapticFeedbackConstants.VIRTUAL_KEY:
-            case HapticFeedbackConstants.EDGE_RELEASE:
-            case HapticFeedbackConstants.CALENDAR_DATE:
-            case HapticFeedbackConstants.CONFIRM:
-            case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
-            case HapticFeedbackConstants.GESTURE_START:
-            case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
-            case HapticFeedbackConstants.SCROLL_LIMIT:
-                return getVibration(effectId, VibrationEffect.EFFECT_CLICK);
-
-            case HapticFeedbackConstants.LONG_PRESS:
-            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
-            case HapticFeedbackConstants.DRAG_START:
-            case HapticFeedbackConstants.EDGE_SQUEEZE:
-                return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
-
-            case HapticFeedbackConstants.REJECT:
-            case HapticFeedbackConstants.BIOMETRIC_REJECT:
-                return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
-
-            case HapticFeedbackConstants.SAFE_MODE_ENABLED:
-                return mSafeModeEnabledVibrationEffect;
-
-            case HapticFeedbackConstants.ASSISTANT_BUTTON:
-                return getAssistantButtonVibration();
-
-            case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
-                return getVibration(
-                        effectId,
-                        VibrationEffect.Composition.PRIMITIVE_TICK,
-                        /* primitiveScale= */ 0.4f,
-                        VibrationEffect.EFFECT_TEXTURE_TICK);
-
-            case HapticFeedbackConstants.TOGGLE_ON:
-                return getVibration(
-                        effectId,
-                        VibrationEffect.Composition.PRIMITIVE_TICK,
-                        /* primitiveScale= */ 0.5f,
-                        VibrationEffect.EFFECT_TICK);
-
-            case HapticFeedbackConstants.TOGGLE_OFF:
-                return getVibration(
-                        effectId,
-                        VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                        /* primitiveScale= */ 0.2f,
-                        VibrationEffect.EFFECT_TEXTURE_TICK);
-
-            case HapticFeedbackConstants.NO_HAPTICS:
-            default:
-                return null;
+    @Nullable public VibrationEffect getVibration(int effectId) {
+        if (!isFeedbackConstantEnabled(effectId)) {
+            return null;
         }
+        VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId);
+        if (customizedVibration != null) {
+            return customizedVibration;
+        }
+        return getVibrationForHapticFeedback(effectId);
     }
 
     /**
+     * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
+     * {@link HapticFeedbackConstants}).
+     *
+     * @param effectId    the haptic feedback effect ID whose respective vibration we want to get.
+     * @param inputSource the {@link InputDevice.Source} that customizes the haptic feedback
+     *                    corresponding to the {@code effectId}.
+     * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
+     * the provided effect ID is not supported.
+     */
+    @Nullable public VibrationEffect getVibration(int effectId, int inputSource) {
+        if (!isFeedbackConstantEnabled(effectId)) {
+            return null;
+        }
+        VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId,
+                inputSource);
+        if (customizedVibration != null) {
+            return customizedVibration;
+        }
+        return getVibrationForHapticFeedback(effectId);
+    }
+
+    // TODO(b/354049335): handle input source customized VibrationAttributes.
+    /**
      * Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
      *
      * @param effectId the haptic feedback effect ID whose respective vibration attributes we want
@@ -255,61 +195,106 @@
         pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
     }
 
-    private VibrationEffect getVibration(int effectId, int predefinedVibrationEffectId) {
-        return getVibration(
-                effectId, predefinedVibrationEffectId, /* fallbackForPredefinedEffect= */ true);
+    private boolean isFeedbackConstantEnabled(int effectId) {
+        return switch (effectId) {
+            case HapticFeedbackConstants.TEXT_HANDLE_MOVE -> mHapticTextHandleEnabled;
+            case HapticFeedbackConstants.NO_HAPTICS -> false;
+            default -> true;
+        };
     }
 
     /**
-     * Returns the customized vibration for {@code hapticFeedbackId}, or
-     * {@code predefinedVibrationEffectId} if a customization does not exist for the haptic
-     * feedback.
-     *
-     * <p>If a customization does not exist and the default predefined effect is to be returned,
-     * {@code fallbackForPredefinedEffect} will be used to decide whether or not to fallback
-     * to a generic pattern if the predefined effect is not hardware supported.
-     *
-     * @see VibrationEffect#get(int, boolean)
+     * Get {@link VibrationEffect} respective {@code effectId} from platform-wise mapping. This
+     * method doesn't include OEM customizations.
      */
-    private VibrationEffect getVibration(
-            int hapticFeedbackId,
-            int predefinedVibrationEffectId,
-            boolean fallbackForPredefinedEffect) {
-        if (effectHasCustomization(hapticFeedbackId)) {
-            return mHapticCustomizations.get(hapticFeedbackId);
+    @Nullable
+    private VibrationEffect getVibrationForHapticFeedback(int effectId) {
+        switch (effectId) {
+            case HapticFeedbackConstants.CONTEXT_CLICK:
+            case HapticFeedbackConstants.GESTURE_END:
+            case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
+            case HapticFeedbackConstants.SCROLL_TICK:
+            case HapticFeedbackConstants.SEGMENT_TICK:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+
+            case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+            case HapticFeedbackConstants.CLOCK_TICK:
+            case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+                // keyboard effect is not customized by the input source.
+                return getKeyboardVibration(effectId);
+
+            case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+            case HapticFeedbackConstants.DRAG_CROSSING:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, /* fallback= */ false);
+
+            case HapticFeedbackConstants.VIRTUAL_KEY:
+            case HapticFeedbackConstants.EDGE_RELEASE:
+            case HapticFeedbackConstants.CALENDAR_DATE:
+            case HapticFeedbackConstants.CONFIRM:
+            case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
+            case HapticFeedbackConstants.GESTURE_START:
+            case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.SCROLL_LIMIT:
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+            case HapticFeedbackConstants.LONG_PRESS:
+            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+            case HapticFeedbackConstants.DRAG_START:
+            case HapticFeedbackConstants.EDGE_SQUEEZE:
+                return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
+            case HapticFeedbackConstants.REJECT:
+            case HapticFeedbackConstants.BIOMETRIC_REJECT:
+                return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+            case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+                // safe mode effect is not customized by the input source.
+                return mSafeModeEnabledVibrationEffect;
+
+            case HapticFeedbackConstants.ASSISTANT_BUTTON:
+                // assistant effect is not customized by the input source.
+                return getAssistantButtonVibration();
+
+            case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
+                return getVibration(
+                        VibrationEffect.Composition.PRIMITIVE_TICK,
+                        /* primitiveScale= */ 0.4f,
+                        VibrationEffect.EFFECT_TEXTURE_TICK);
+
+            case HapticFeedbackConstants.TOGGLE_ON:
+                return getVibration(
+                        VibrationEffect.Composition.PRIMITIVE_TICK,/* primitiveScale= */ 0.5f,
+                        VibrationEffect.EFFECT_TICK);
+
+            case HapticFeedbackConstants.TOGGLE_OFF:
+                return getVibration(
+                        VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                        /* primitiveScale= */ 0.2f,
+                        VibrationEffect.EFFECT_TEXTURE_TICK);
+
+            case HapticFeedbackConstants.NO_HAPTICS:
+            default:
+                return null;
         }
-        return VibrationEffect.get(predefinedVibrationEffectId, fallbackForPredefinedEffect);
     }
 
-    /**
-     * Returns the customized vibration for {@code hapticFeedbackId}, or some fallback vibration if
-     * a customization does not exist for the ID.
-     *
-     * <p>The fallback will be a primitive composition formed of {@code primitiveId} and
-     * {@code primitiveScale}, if the primitive is supported. Otherwise, it will be a predefined
-     * vibration of {@code elsePredefinedVibrationEffectId}.
-     */
-    private VibrationEffect getVibration(
-            int hapticFeedbackId,
-            int primitiveId,
-            float primitiveScale,
-            int elsePredefinedVibrationEffectId) {
-        if (effectHasCustomization(hapticFeedbackId)) {
-            return mHapticCustomizations.get(hapticFeedbackId);
-        }
+    @NonNull
+    private VibrationEffect getVibration(int primitiveId, float primitiveScale,
+            int predefinedVibrationEffectId) {
         if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
             return VibrationEffect.startComposition()
                     .addPrimitive(primitiveId, primitiveScale)
                     .compose();
-        } else {
-            return VibrationEffect.get(elsePredefinedVibrationEffectId);
         }
+        return VibrationEffect.get(predefinedVibrationEffectId);
     }
 
+    @NonNull
     private VibrationEffect getAssistantButtonVibration() {
-        if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
-            return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
-        }
         if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
                 && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
             // quiet ramp, short pause, then sharp tick
@@ -322,15 +307,8 @@
         return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
     }
 
-    private boolean effectHasCustomization(int effectId) {
-        return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
-    }
-
+    @NonNull
     private VibrationEffect getKeyboardVibration(int effectId) {
-        if (effectHasCustomization(effectId)) {
-            return mHapticCustomizations.get(effectId);
-        }
-
         int primitiveId;
         int predefinedEffectId;
         boolean predefinedEffectFallback;
@@ -354,8 +332,7 @@
                         .compose();
             }
         }
-        return getVibration(effectId, predefinedEffectId,
-                /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+        return VibrationEffect.get(predefinedEffectId, predefinedEffectFallback);
     }
 
     private VibrationAttributes createKeyboardVibrationAttributes(
@@ -367,17 +344,6 @@
         return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
     }
 
-    @Nullable
-    private static SparseArray<VibrationEffect> loadHapticCustomizations(
-            Resources res, VibratorInfo vibratorInfo) {
-        try {
-            return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
-        } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
-            Slog.e(TAG, "Unable to load haptic customizations.", e);
-            return null;
-        }
-    }
-
     private static boolean shouldBypassInterruptionPolicy(int effectId) {
         switch (effectId) {
             case HapticFeedbackConstants.SCROLL_TICK:
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 4e58b9a..83e05f4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -26,6 +26,7 @@
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
 
 /** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
 final class InputDeviceDelegate implements InputManager.InputDeviceListener {
@@ -93,7 +94,7 @@
      *
      * @return {@link #isAvailable()}
      */
-    public boolean vibrateIfAvailable(Vibration.CallerInfo callerInfo, CombinedVibration effect) {
+    public boolean vibrateIfAvailable(CallerInfo callerInfo, CombinedVibration effect) {
         synchronized (mLock) {
             for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
                 mInputDeviceVibrators.valueAt(i).vibrate(callerInfo.uid, callerInfo.opPkg, effect,
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index aa4b9f3..9a04793 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -31,7 +31,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
-import java.io.PrintWriter;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -52,131 +51,69 @@
     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
 
     public final long id;
-    public final CallerInfo callerInfo;
+    public final VibrationSession.CallerInfo callerInfo;
     public final VibrationStats stats = new VibrationStats();
     public final IBinder callerToken;
 
-    /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
-    enum Status {
-        UNKNOWN(VibrationProto.UNKNOWN),
-        RUNNING(VibrationProto.RUNNING),
-        FINISHED(VibrationProto.FINISHED),
-        FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
-        FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
-        CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
-        CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
-        CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
-        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
-        CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
-        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
-        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
-        CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
-        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
-        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
-        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
-        IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
-        IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
-        IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
-        IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
-        IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
-        IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
-        IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
-        IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
-        IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
-        IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
-        IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
-        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
-        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
-        IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
+    private VibrationSession.Status mStatus;
 
-        private final int mProtoEnumValue;
-
-        Status(int value) {
-            mProtoEnumValue = value;
-        }
-
-        public int getProtoEnumValue() {
-            return mProtoEnumValue;
-        }
-    }
-
-    Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) {
+    Vibration(@NonNull IBinder token, @NonNull VibrationSession.CallerInfo callerInfo) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(callerInfo);
+        mStatus = VibrationSession.Status.RUNNING;
         this.id = sNextVibrationId.getAndIncrement();
         this.callerToken = token;
         this.callerInfo = callerInfo;
     }
 
+    VibrationSession.Status getStatus() {
+        return mStatus;
+    }
+
+    /** Return true is current status is different from {@link VibrationSession.Status#RUNNING}. */
+    boolean hasEnded() {
+        return mStatus != VibrationSession.Status.RUNNING;
+    }
+
+    /**
+     * Set the {@link VibrationSession} of this vibration and reports the current system time as
+     * this vibration end time, for debugging purposes.
+     *
+     * <p>This method will only accept given value if the current status is {@link
+     * VibrationSession.Status#RUNNING}.
+     */
+    void end(Vibration.EndInfo endInfo) {
+        if (hasEnded()) {
+            // Vibration already ended, keep first ending status set and ignore this one.
+            return;
+        }
+        mStatus = endInfo.status;
+        stats.reportEnded(endInfo.endedBy);
+    }
+
     /** Return true if vibration is a repeating vibration. */
     abstract boolean isRepeating();
 
-    /**
-     * Holds lightweight immutable info on the process that triggered the vibration. This data
-     * could potentially be kept in memory for a long time for bugreport dumpsys operations.
-     *
-     * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to
-     * potentially expensive or resource-linked objects, such as {@link IBinder}.
-     */
-    static final class CallerInfo {
-        public final VibrationAttributes attrs;
-        public final int uid;
-        public final int deviceId;
-        public final String opPkg;
-        public final String reason;
+    /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
+    abstract VibrationSession.DebugInfo getDebugInfo();
 
-        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
-                String reason) {
-            Objects.requireNonNull(attrs);
-            this.attrs = attrs;
-            this.uid = uid;
-            this.deviceId = deviceId;
-            this.opPkg = opPkg;
-            this.reason = reason;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof CallerInfo)) return false;
-            CallerInfo that = (CallerInfo) o;
-            return Objects.equals(attrs, that.attrs)
-                    && uid == that.uid
-                    && deviceId == that.deviceId
-                    && Objects.equals(opPkg, that.opPkg)
-                    && Objects.equals(reason, that.reason);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(attrs, uid, deviceId, opPkg, reason);
-        }
-
-        @Override
-        public String toString() {
-            return "CallerInfo{"
-                    + " uid=" + uid
-                    + ", opPkg=" + opPkg
-                    + ", deviceId=" + deviceId
-                    + ", attrs=" + attrs
-                    + ", reason=" + reason
-                    + '}';
-        }
-    }
+    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+    abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);
 
     /** Immutable info passed as a signal to end a vibration. */
     static final class EndInfo {
-        /** The {@link Status} to be set to the vibration when it ends with this info. */
+        /** The vibration status to be set when it ends with this info. */
         @NonNull
-        public final Status status;
+        public final VibrationSession.Status status;
         /** Info about the process that ended the vibration. */
-        public final CallerInfo endedBy;
+        public final VibrationSession.CallerInfo endedBy;
 
-        EndInfo(@NonNull Vibration.Status status) {
+        EndInfo(@NonNull VibrationSession.Status status) {
             this(status, null);
         }
 
-        EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) {
+        EndInfo(@NonNull VibrationSession.Status status,
+                @Nullable VibrationSession.CallerInfo endedBy) {
             this.status = status;
             this.endedBy = endedBy;
         }
@@ -211,10 +148,10 @@
      * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
      * potentially expensive or resource-linked objects, such as {@link IBinder}.
      */
-    static final class DebugInfo {
-        final Status mStatus;
+    static final class DebugInfoImpl implements VibrationSession.DebugInfo {
+        final VibrationSession.Status mStatus;
         final long mCreateTime;
-        final CallerInfo mCallerInfo;
+        final VibrationSession.CallerInfo mCallerInfo;
         @Nullable
         final CombinedVibration mPlayedEffect;
 
@@ -226,9 +163,10 @@
         private final int mScaleLevel;
         private final float mAdaptiveScale;
 
-        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
+        DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
+                @Nullable CombinedVibration playedEffect,
                 @Nullable CombinedVibration originalEffect, int scaleLevel,
-                float adaptiveScale, @NonNull CallerInfo callerInfo) {
+                float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
             Objects.requireNonNull(callerInfo);
             mCreateTime = stats.getCreateTimeDebug();
             mStartTime = stats.getStartTimeDebug();
@@ -243,6 +181,27 @@
         }
 
         @Override
+        public VibrationSession.Status getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public long getCreateUptimeMillis() {
+            return mCreateTime;
+        }
+
+        @Override
+        public VibrationSession.CallerInfo getCallerInfo() {
+            return mCallerInfo;
+        }
+
+        @Nullable
+        @Override
+        public Object getDumpAggregationKey() {
+            return mPlayedEffect;
+        }
+
+        @Override
         public String toString() {
             return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
                     + ", startTime: " + formatTime(mStartTime, /*includeDate=*/ true)
@@ -257,17 +216,13 @@
                     + ", callerInfo: " + mCallerInfo;
         }
 
-        void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+        @Override
+        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
             statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
         }
 
-        /**
-         * Write this info in a compact way into given {@link PrintWriter}.
-         *
-         * <p>This is used by dumpsys to log multiple vibration records in single lines that are
-         * easy to skim through by the sorted created time.
-         */
-        void dumpCompact(IndentingPrintWriter pw) {
+        @Override
+        public void dumpCompact(IndentingPrintWriter pw) {
             boolean isExternalVibration = mPlayedEffect == null;
             String timingsStr = String.format(Locale.ROOT,
                     "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
@@ -299,8 +254,8 @@
             pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr);
         }
 
-        /** Write this info into given {@link PrintWriter}. */
-        void dump(IndentingPrintWriter pw) {
+        @Override
+        public void dump(IndentingPrintWriter pw) {
             pw.println("Vibration:");
             pw.increaseIndent();
             pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
@@ -317,8 +272,8 @@
             pw.decreaseIndent();
         }
 
-        /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
-        void dump(ProtoOutputStream proto, long fieldId) {
+        @Override
+        public void dump(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
             proto.write(VibrationProto.START_TIME, mStartTime);
             proto.write(VibrationProto.END_TIME, mEndTime);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
new file mode 100644
index 0000000..5640b49
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Represents a generic vibration session that plays one or more vibration requests.
+ *
+ * <p>This might represent:
+ *
+ * <ol>
+ *     <li>A single {@link CombinedVibration} playback.
+ *     <li>An {@link android.os.ExternalVibration} playback.
+ * </ol>
+ */
+interface VibrationSession {
+
+    /** Returns data about the client app that triggered this vibration session. */
+    CallerInfo getCallerInfo();
+
+    /** Returns debug data for logging and metric reports. */
+    DebugInfo getDebugInfo();
+
+    /**
+     * Links this session to the app process death with given callback to handle it.
+     *
+     * <p>This can be used by the service to end the vibration session when the app process dies.
+     */
+    void linkToDeath(Runnable callback);
+
+    /** Removes link to the app process death. */
+    void unlinkToDeath();
+
+    /** Notify the session end was requested, which might be acted upon asynchronously. */
+    void notifyEnded();
+
+    /**
+     * Session status with reference to values from vibratormanagerservice.proto for logging.
+     */
+    enum Status {
+        UNKNOWN(VibrationProto.UNKNOWN),
+        RUNNING(VibrationProto.RUNNING),
+        FINISHED(VibrationProto.FINISHED),
+        FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+        FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+        CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+        CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+        CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+        CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
+        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+        CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
+        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+        IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+        IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+        IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+        IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
+        IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+        IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+        IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+        IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+        IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+        IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+        IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
+        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
+        IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
+
+        private final int mProtoEnumValue;
+
+        Status(int value) {
+            mProtoEnumValue = value;
+        }
+
+        public int getProtoEnumValue() {
+            return mProtoEnumValue;
+        }
+    }
+
+    /**
+     * Holds lightweight immutable info on the process that triggered the vibration session.
+     *
+     * <p>This data could potentially be kept in memory for a long time for bugreport dumpsys
+     * operations. It shouldn't hold any references to potentially expensive or resource-linked
+     * objects, such as {@link IBinder}.
+     */
+    final class CallerInfo {
+        public final VibrationAttributes attrs;
+        public final int uid;
+        public final int deviceId;
+        public final String opPkg;
+        public final String reason;
+
+        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
+                String reason) {
+            Objects.requireNonNull(attrs);
+            this.attrs = attrs;
+            this.uid = uid;
+            this.deviceId = deviceId;
+            this.opPkg = opPkg;
+            this.reason = reason;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CallerInfo)) return false;
+            CallerInfo that = (CallerInfo) o;
+            return Objects.equals(attrs, that.attrs)
+                    && uid == that.uid
+                    && deviceId == that.deviceId
+                    && Objects.equals(opPkg, that.opPkg)
+                    && Objects.equals(reason, that.reason);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(attrs, uid, deviceId, opPkg, reason);
+        }
+
+        @Override
+        public String toString() {
+            return "CallerInfo{"
+                    + " uid=" + uid
+                    + ", opPkg=" + opPkg
+                    + ", deviceId=" + deviceId
+                    + ", attrs=" + attrs
+                    + ", reason=" + reason
+                    + '}';
+        }
+    }
+
+    /**
+     * Interface for lightweight debug information about the vibration session for debugging.
+     *
+     * <p>This data could potentially be kept in memory for a long time for bugreport dumpsys
+     * operations. It shouldn't hold any references to potentially expensive or resource-linked
+     * objects, such as {@link IBinder}.
+     */
+    interface DebugInfo {
+
+        /** Return the vibration session status. */
+        Status getStatus();
+
+        /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
+        long getCreateUptimeMillis();
+
+        /** Returns information about the process that created the session. */
+        CallerInfo getCallerInfo();
+
+        /**
+         * Returns the aggregation key for log records.
+         *
+         * <p>This is used to aggregate similar vibration sessions triggered in quick succession
+         * (e.g. multiple keyboard vibrations when the user is typing).
+         *
+         * <p>This does not need to include data from {@link CallerInfo} or {@link Status}.
+         *
+         * @see GroupedAggregatedLogRecords
+         */
+        @Nullable
+        Object getDumpAggregationKey();
+
+        /** Logs vibration session fields for metric reports. */
+        void logMetrics(VibratorFrameworkStatsLogger statsLogger);
+
+        /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
+        void dump(ProtoOutputStream proto, long fieldId);
+
+        /** Write this info into given {@link PrintWriter}. */
+        void dump(IndentingPrintWriter pw);
+
+        /**
+         * Write this info in a compact way into given {@link PrintWriter}.
+         *
+         * <p>This is used by dumpsys to log multiple records in single lines that are easy to skim
+         * through by the sorted created time.
+         */
+        void dumpCompact(IndentingPrintWriter pw);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 0d6778c..69cdcf4 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -66,6 +66,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -416,46 +418,46 @@
     /**
      * Check if given vibration should be ignored by the service.
      *
-     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored,
+     * @return One of VibrationSession.Status.IGNORED_* values if the vibration should be ignored,
      * null otherwise.
      */
     @Nullable
-    public Vibration.Status shouldIgnoreVibration(@NonNull Vibration.CallerInfo callerInfo) {
+    public Status shouldIgnoreVibration(@NonNull CallerInfo callerInfo) {
         final int usage = callerInfo.attrs.getUsage();
         synchronized (mLock) {
             if (!mUidObserver.isUidForeground(callerInfo.uid)
                     && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
-                return Vibration.Status.IGNORED_BACKGROUND;
+                return Status.IGNORED_BACKGROUND;
             }
 
             if (callerInfo.deviceId != Context.DEVICE_ID_DEFAULT
                     && callerInfo.deviceId != Context.DEVICE_ID_INVALID) {
-                return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+                return Status.IGNORED_FROM_VIRTUAL_DEVICE;
             }
 
             if (callerInfo.deviceId == Context.DEVICE_ID_INVALID
                     && isAppRunningOnAnyVirtualDevice(callerInfo.uid)) {
-                return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+                return Status.IGNORED_FROM_VIRTUAL_DEVICE;
             }
 
             if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
-                return Vibration.Status.IGNORED_FOR_POWER;
+                return Status.IGNORED_FOR_POWER;
             }
 
             if (!callerInfo.attrs.isFlagSet(
                     VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                     && !shouldVibrateForUserSetting(callerInfo)) {
-                return Vibration.Status.IGNORED_FOR_SETTINGS;
+                return Status.IGNORED_FOR_SETTINGS;
             }
 
             if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
                 if (!shouldVibrateForRingerModeLocked(usage)) {
-                    return Vibration.Status.IGNORED_FOR_RINGER_MODE;
+                    return Status.IGNORED_FOR_RINGER_MODE;
                 }
             }
 
             if (mVibrationConfig.ignoreVibrationsOnWirelessCharger() && mOnWirelessCharger) {
-                return Vibration.Status.IGNORED_ON_WIRELESS_CHARGER;
+                return Status.IGNORED_ON_WIRELESS_CHARGER;
             }
         }
         return null;
@@ -471,7 +473,7 @@
      *
      * @return true if the vibration should be cancelled when the screen goes off, false otherwise.
      */
-    public boolean shouldCancelVibrationOnScreenOff(@NonNull Vibration.CallerInfo callerInfo,
+    public boolean shouldCancelVibrationOnScreenOff(@NonNull CallerInfo callerInfo,
             long vibrationStartUptimeMillis) {
         PowerManagerInternal pm;
         synchronized (mLock) {
@@ -483,8 +485,8 @@
             // ignored here and not cancel a vibration, and those are usually triggered by timeout
             // or inactivity, so it's unlikely that it will override a more active goToSleep reason.
             PowerManager.SleepData sleepData = pm.getLastGoToSleep();
-            if ((sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis)
-                    || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason)) {
+            if (sleepData != null && (sleepData.goToSleepUptimeMillis < vibrationStartUptimeMillis
+                    || POWER_MANAGER_SLEEP_REASON_ALLOWLIST.contains(sleepData.goToSleepReason))) {
                 // Ignore screen off events triggered before the vibration started, and all
                 // automatic "go to sleep" events from allowlist.
                 Slog.d(TAG, "Ignoring screen off event triggered at uptime "
@@ -522,7 +524,7 @@
      * {@code false} to ignore the vibration.
      */
     @GuardedBy("mLock")
-    private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) {
+    private boolean shouldVibrateForUserSetting(CallerInfo callerInfo) {
         final int usage = callerInfo.attrs.getUsage();
         if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
             // Main setting disabled.
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index 8179d6a..fc0c6e7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -166,7 +166,7 @@
      * @return true if the status was accepted. This method will only accept given values if
      * the end timestamp was never set.
      */
-    boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) {
+    boolean reportEnded(@Nullable VibrationSession.CallerInfo endedBy) {
         if (hasEnded()) {
             // Vibration already ended, keep first ending stats set and ignore this one.
             return false;
@@ -187,7 +187,7 @@
      * <p>This method will only accept the first value as the one that was interrupted by this
      * vibration, and will ignore all successive calls.
      */
-    void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) {
+    void reportInterruptedAnotherVibration(@NonNull VibrationSession.CallerInfo callerInfo) {
         if (mInterruptedUsage < 0) {
             mInterruptedUsage = callerInfo.attrs.getUsage();
         }
@@ -330,7 +330,7 @@
         public final int[] halUnsupportedEffectsUsed;
         private boolean mIsWritten;
 
-        StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+        StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status,
                 VibrationStats stats, long completionUptimeMillis) {
             this.uid = uid;
             this.vibrationType = vibrationType;
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 7152844..5137d19 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -32,6 +32,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -217,7 +218,7 @@
     }
 
     /**
-     * Calculate the {@link Vibration.Status} based on the current queue state and the expected
+     * Calculate the {@link Vibration.EndInfo} based on the current queue state and the expected
      * number of {@link StartSequentialEffectStep} to be played.
      */
     @Nullable
@@ -235,10 +236,10 @@
         }
         // No pending steps, and something happened.
         if (mSuccessfulVibratorOnSteps > 0) {
-            return new Vibration.EndInfo(Vibration.Status.FINISHED);
+            return new Vibration.EndInfo(Status.FINISHED);
         }
         // If no step was able to turn the vibrator ON successfully.
-        return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
+        return new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED);
     }
 
     /**
@@ -352,7 +353,7 @@
         if (DEBUG) {
             Slog.d(TAG, "Binder died, cancelling vibration...");
         }
-        notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+        notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
                 /* immediate= */ false);
     }
 
@@ -377,7 +378,7 @@
         if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
             Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
                     + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
-            cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
+            cancelInfo = new Vibration.EndInfo(Status.CANCELLED_BY_UNKNOWN_REASON);
         }
         synchronized (mLock) {
             if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cfb4c74..ab4a4d8 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -240,7 +241,7 @@
                 runCurrentVibrationWithWakeLockAndDeathLink();
             } finally {
                 clientVibrationCompleteIfNotAlready(
-                        new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
+                        new Vibration.EndInfo(Status.FINISHED_UNEXPECTED));
             }
         } finally {
             mWakeLock.release();
@@ -259,7 +260,7 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Error linking vibration to token death", e);
             clientVibrationCompleteIfNotAlready(
-                    new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
+                    new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN));
             return;
         }
         // Ensure that the unlink always occurs now.
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index de5e662..3a814cd 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -559,8 +559,8 @@
     }
 
     /**
-     * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
-     * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+     * Record for a single {@link VibrationSession.DebugInfo}, that can be grouped by usage and
+     * aggregated by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
      */
     private static final class VibrationScaleParamRecord
             implements GroupedAggregatedLogRecords.SingleLogRecord {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index dd16d24..799934a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -73,9 +73,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.SystemService;
 import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.DebugInfo;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -160,11 +162,12 @@
     @GuardedBy("mLock")
     private VibrationStepConductor mNextVibration;
     @GuardedBy("mLock")
-    private ExternalVibrationHolder mCurrentExternalVibration;
+    private ExternalVibrationSession mCurrentExternalVibration;
     @GuardedBy("mLock")
     private boolean mServiceReady;
 
-    private final VibrationSettings mVibrationSettings;
+    @VisibleForTesting
+    final VibrationSettings mVibrationSettings;
     private final VibrationScaler mVibrationScaler;
     private final VibratorControlService mVibratorControlService;
     private final InputDeviceDelegate mInputDeviceDelegate;
@@ -184,13 +187,12 @@
                     // When the system is entering a non-interactive state, we want to cancel
                     // vibrations in case a misbehaving app has abandoned them.
                     if (shouldCancelOnScreenOffLocked(mNextVibration)) {
-                        clearNextVibrationLocked(
-                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
+                        clearNextVibrationLocked(new Vibration.EndInfo(
+                                Status.CANCELLED_BY_SCREEN_OFF));
                     }
                     if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(
-                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                                /* immediate= */ false);
+                        mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
+                                Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
                     }
                 }
             } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
@@ -198,12 +200,11 @@
                 synchronized (mLock) {
                     if (shouldCancelOnFgUserRequest(mNextVibration)) {
                         clearNextVibrationLocked(new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_FOREGROUND_USER));
+                                Status.CANCELLED_BY_FOREGROUND_USER));
                     }
                     if (shouldCancelOnFgUserRequest(mCurrentVibration)) {
                         mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
-                                        Vibration.Status.CANCELLED_BY_FOREGROUND_USER),
-                                /* immediate= */ false);
+                                Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false);
                     }
                 }
             }
@@ -220,12 +221,12 @@
                     }
                     synchronized (mLock) {
                         if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
-                            clearNextVibrationLocked(
-                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS));
+                            clearNextVibrationLocked(new Vibration.EndInfo(
+                                    Status.CANCELLED_BY_APP_OPS));
                         }
                         if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
                             mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
-                                    Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+                                    Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
                         }
                     }
                 }
@@ -441,8 +442,8 @@
                     return false;
                 }
                 AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId,
-                        new Vibration.CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg,
-                                null), effects);
+                        new CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg, null),
+                        effects);
                 mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration);
                 updateAlwaysOnLocked(alwaysOnVibration);
             }
@@ -487,39 +488,18 @@
     HalVibration performHapticFeedbackInternal(
             int uid, int deviceId, String opPkg, int constant, String reason,
             IBinder token, int flags, int privFlags) {
-
         // Make sure we report the constant id in the requested haptic feedback reason.
         reason = "performHapticFeedback(constant=" + constant + "): " + reason;
-
         HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
-        if (hapticVibrationProvider == null) {
-            Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
-            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
-                    Vibration.Status.IGNORED_ERROR_SCHEDULING);
+        Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason, hapticVibrationProvider);
+        if (ignoreStatus != null) {
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
             return null;
         }
-
-        if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
-                && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
-            Slog.w(TAG, "performHapticFeedback; no permission for system constant " + constant);
-            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
-                    Vibration.Status.IGNORED_MISSING_PERMISSION);
-            return null;
-        }
-
-        VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
-        if (effect == null) {
-            Slog.w(TAG, "performHapticFeedback; vibration absent for constant " + constant);
-            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
-                    Vibration.Status.IGNORED_UNSUPPORTED);
-            return null;
-        }
-
-        CombinedVibration vib = CombinedVibration.createParallel(effect);
-        VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
-                constant, flags, privFlags);
-        VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
-        return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
+        return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+                hapticVibrationProvider.getVibration(constant),
+                hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+                        constant, flags, privFlags));
     }
 
     /**
@@ -532,12 +512,34 @@
     HalVibration performHapticFeedbackForInputDeviceInternal(
             int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource,
             String reason, IBinder token, int flags, int privFlags) {
-        // TODO(b/355543835): implement input device specific logic.
-        if (DEBUG) {
-            Slog.d(TAG, "performHapticFeedbackForInput: input device specific not implemented.");
+        // Make sure we report the constant id in the requested haptic feedback reason.
+        reason = "performHapticFeedbackForInputDevice(constant=" + constant + ", inputDeviceId="
+                + inputDeviceId + ", inputSource=" + inputSource + "): " + reason;
+        HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+        Status ignoreStatus = shouldIgnoreHapticFeedback(constant, reason, hapticVibrationProvider);
+        if (ignoreStatus != null) {
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason, ignoreStatus);
+            return null;
         }
-        return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
-                this, flags, privFlags);
+        return performHapticFeedbackWithEffect(uid, deviceId, opPkg, constant, reason, token,
+                hapticVibrationProvider.getVibration(constant, inputSource),
+                hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+                        constant, flags, privFlags));
+    }
+
+    private HalVibration performHapticFeedbackWithEffect(int uid, int deviceId, String opPkg,
+            int constant, String reason, IBinder token, VibrationEffect effect,
+            VibrationAttributes attrs) {
+        if (effect == null) {
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+                    Status.IGNORED_UNSUPPORTED);
+            Slog.w(TAG,
+                    "performHapticFeedbackWithEffect; vibration absent for constant " + constant);
+            return null;
+        }
+        CombinedVibration vib = CombinedVibration.createParallel(effect);
+        VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
+        return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
     }
 
     /**
@@ -575,23 +577,21 @@
     private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
             @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
             String reason, IBinder token) {
-        Vibration.CallerInfo callerInfo =
-                new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason);
+        CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason);
         if (token == null) {
             Slog.e(TAG, "token must not be null");
-            logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_ERROR_TOKEN);
+            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
             return null;
         }
         if (effect.hasVendorEffects()
                 && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
             Slog.e(TAG, "vibrate; no permission for vendor effects");
-            logAndRecordVibrationAttempt(effect, callerInfo,
-                    Vibration.Status.IGNORED_MISSING_PERMISSION);
+            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
             return null;
         }
         enforceUpdateAppOpsStatsPermission(uid);
         if (!isEffectValid(effect)) {
-            logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_UNSUPPORTED);
+            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
             return null;
         }
         // Create Vibration.Stats as close to the received request as possible, for tracking.
@@ -622,11 +622,11 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mCurrentExternalVibration != null) {
-                        mCurrentExternalVibration.mute();
+                        mCurrentExternalVibration.notifyEnded();
                         vib.stats.reportInterruptedAnotherVibration(
                                 mCurrentExternalVibration.callerInfo);
                         endExternalVibrateLocked(
-                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
                                         vib.callerInfo),
                                 /* continueExternalControl= */ false);
                     } else if (mCurrentVibration != null) {
@@ -642,7 +642,7 @@
                             vib.stats.reportInterruptedAnotherVibration(
                                     mCurrentVibration.getVibration().callerInfo);
                             mCurrentVibration.notifyCancelled(
-                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                    new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
                                             vib.callerInfo),
                                     /* immediate= */ false);
                         }
@@ -674,7 +674,7 @@
                     Slog.d(TAG, "Canceling vibration");
                 }
                 Vibration.EndInfo cancelledByUserInfo =
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
+                        new Vibration.EndInfo(Status.CANCELLED_BY_USER);
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNextVibration != null
@@ -690,9 +690,9 @@
                     }
                     if (mCurrentExternalVibration != null
                             && shouldCancelVibration(
-                            mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
+                            mCurrentExternalVibration.getCallerInfo().attrs,
                             usageFilter)) {
-                        mCurrentExternalVibration.mute();
+                        mCurrentExternalVibration.notifyEnded();
                         endExternalVibrateLocked(
                                 cancelledByUserInfo, /* continueExternalControl= */ false);
                     }
@@ -857,7 +857,7 @@
                             : vibrationEndInfo.status));
                 }
                 mCurrentVibration.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false);
             }
         }
@@ -908,7 +908,7 @@
             // Note that we don't consider pipelining here, because new pipelined ones should
             // replace pending non-executing pipelined ones anyway.
             clearNextVibrationLocked(
-                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, vib.callerInfo));
+                    new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo));
             mNextVibration = conductor;
             return null;
         } finally {
@@ -931,15 +931,15 @@
                     if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
                         // Shouldn't happen. The method call already logs a wtf.
                         mCurrentVibration = null;  // Aborted.
-                        return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_SCHEDULING);
+                        return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING);
                     }
                     return null;
                 case AppOpsManager.MODE_ERRORED:
                     Slog.w(TAG, "Start AppOpsManager operation errored for uid "
                             + vib.callerInfo.uid);
-                    return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
+                    return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
                 default:
-                    return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
+                    return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -947,7 +947,7 @@
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
+    private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
             boolean shouldWriteStats) {
         vib.end(vibrationEndInfo);
         logAndRecordVibration(vib.getDebugInfo());
@@ -957,15 +957,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
-            Vibration.EndInfo vibrationEndInfo) {
-        vib.end(vibrationEndInfo);
-        logAndRecordVibration(vib.getDebugInfo());
-        mFrameworkStatsLogger.writeVibrationReportedAsync(
-                vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
-    }
-
     private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
         CompletableFuture<Void> requestVibrationParamsFuture = null;
 
@@ -987,33 +978,32 @@
         vib.scaleEffects(mVibrationScaler);
         mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
 
-        return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+        return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES);
     }
 
     private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
-            String reason, Vibration.Status status) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+            String reason, Status status) {
+        CallerInfo callerInfo = new CallerInfo(
                 VibrationAttributes.createForUsage(VibrationAttributes.USAGE_UNKNOWN),
                 uid, deviceId, opPkg, reason);
         logAndRecordVibrationAttempt(/* effect= */ null, callerInfo, status);
     }
 
     private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
-            Vibration.CallerInfo callerInfo, Vibration.Status status) {
+            CallerInfo callerInfo, Status status) {
         logAndRecordVibration(
-                new Vibration.DebugInfo(status, new VibrationStats(),
+                new Vibration.DebugInfoImpl(status, new VibrationStats(),
                         effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
                         VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
     }
 
-    private void logAndRecordVibration(Vibration.DebugInfo info) {
+    private void logAndRecordVibration(DebugInfo info) {
         info.logMetrics(mFrameworkStatsLogger);
-        logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+        logVibrationStatus(info.getCallerInfo().uid, info.getCallerInfo().attrs, info.getStatus());
         mVibratorManagerRecords.record(info);
     }
 
-    private void logVibrationStatus(int uid, VibrationAttributes attrs,
-            Vibration.Status status) {
+    private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) {
         switch (status) {
             case IGNORED_BACKGROUND:
                 Slog.e(TAG, "Ignoring incoming vibration as process with"
@@ -1158,15 +1148,14 @@
 
         if (ongoingVibrationImportance > newVibrationImportance) {
             // Existing vibration has higher importance and should not be cancelled.
-            return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE,
+            return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE,
                     ongoingVibration.callerInfo);
         }
 
         // Same importance, use repeating as a tiebreaker.
         if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
             // Ongoing vibration is repeating and new one is not, give priority to ongoing
-            return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_ONGOING,
-                    ongoingVibration.callerInfo);
+            return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo);
         }
         // New vibration is repeating or this is a complete tie between them,
         // give priority to new vibration.
@@ -1217,8 +1206,8 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) {
-        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
+    private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) {
+        Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
         if (statusFromSettings != null) {
             return new Vibration.EndInfo(statusFromSettings);
         }
@@ -1228,15 +1217,30 @@
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
                 // want to throw a SecurityException here.
-                return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
+                return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS);
             } else {
-                return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
+                return new Vibration.EndInfo(Status.IGNORED_APP_OPS);
             }
         }
 
         return null;
     }
 
+    @Nullable
+    private Status shouldIgnoreHapticFeedback(int constant, String reason,
+            HapticFeedbackVibrationProvider hapticVibrationProvider) {
+        if (hapticVibrationProvider == null) {
+            Slog.e(TAG, reason + "; haptic vibration provider not ready.");
+            return Status.IGNORED_ERROR_SCHEDULING;
+        }
+        if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
+                && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
+            Slog.w(TAG, reason + "; no permission for system constant " + constant);
+            return Status.IGNORED_MISSING_PERMISSION;
+        }
+        return null;
+    }
+
     /**
      * Return true if the vibration has the same token and usage belongs to given usage class.
      *
@@ -1273,7 +1277,7 @@
      * {@code attrs}. This will return one of the AppOpsManager.MODE_*.
      */
     @GuardedBy("mLock")
-    private int checkAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+    private int checkAppOpModeLocked(CallerInfo callerInfo) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
                 callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg);
         int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs);
@@ -1288,7 +1292,7 @@
 
     /** Start an operation in {@link AppOpsManager}, if allowed. */
     @GuardedBy("mLock")
-    private int startAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+    private int startAppOpModeLocked(CallerInfo callerInfo) {
         return fixupAppOpModeLocked(
                 mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg),
                 callerInfo.attrs);
@@ -1299,7 +1303,7 @@
      * operation with same uid was previously started.
      */
     @GuardedBy("mLock")
-    private void finishAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+    private void finishAppOpModeLocked(CallerInfo callerInfo) {
         mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg);
     }
 
@@ -1717,10 +1721,10 @@
      */
     private static final class AlwaysOnVibration {
         public final int alwaysOnId;
-        public final Vibration.CallerInfo callerInfo;
+        public final CallerInfo callerInfo;
         public final SparseArray<PrebakedSegment> effects;
 
-        AlwaysOnVibration(int alwaysOnId, Vibration.CallerInfo callerInfo,
+        AlwaysOnVibration(int alwaysOnId, CallerInfo callerInfo,
                 SparseArray<PrebakedSegment> effects) {
             this.alwaysOnId = alwaysOnId;
             this.callerInfo = callerInfo;
@@ -1728,113 +1732,6 @@
         }
     }
 
-    /** Holder for a {@link ExternalVibration}. */
-    private final class ExternalVibrationHolder extends Vibration implements
-            IBinder.DeathRecipient {
-
-        public final ExternalVibration externalVibration;
-        public final ExternalVibrationScale scale = new ExternalVibrationScale();
-
-        private Vibration.Status mStatus;
-
-        private ExternalVibrationHolder(ExternalVibration externalVibration) {
-            super(externalVibration.getToken(), new Vibration.CallerInfo(
-                    externalVibration.getVibrationAttributes(), externalVibration.getUid(),
-                    // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
-                    // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
-                    Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
-            this.externalVibration = externalVibration;
-            mStatus = Vibration.Status.RUNNING;
-        }
-
-        public void muteScale() {
-            scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
-            if (Flags.hapticsScaleV2Enabled()) {
-                scale.scaleFactor = 0;
-            }
-        }
-
-        public void scale(VibrationScaler scaler, int usage) {
-            scale.scaleLevel = scaler.getScaleLevel(usage);
-            if (Flags.hapticsScaleV2Enabled()) {
-                scale.scaleFactor = scaler.getScaleFactor(usage);
-            }
-            scale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
-            stats.reportAdaptiveScale(scale.adaptiveHapticsScale);
-        }
-
-        public void mute() {
-            externalVibration.mute();
-        }
-
-        public void linkToDeath() {
-            externalVibration.linkToDeath(this);
-        }
-
-        public void unlinkToDeath() {
-            externalVibration.unlinkToDeath(this);
-        }
-
-        public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
-            return this.externalVibration.equals(externalVibration);
-        }
-
-        public void end(Vibration.EndInfo info) {
-            if (mStatus != Vibration.Status.RUNNING) {
-                // Already ended, ignore this call
-                return;
-            }
-            mStatus = info.status;
-            stats.reportEnded(info.endedBy);
-
-            if (stats.hasStarted()) {
-                // External vibration doesn't have feedback from total time the vibrator was playing
-                // with non-zero amplitude, so we use the duration between start and end times of
-                // the vibration as the time the vibrator was ON, since the haptic channels are
-                // open for this duration and can receive vibration waveform data.
-                stats.reportVibratorOn(
-                        stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
-            }
-        }
-
-        public void binderDied() {
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "External vibration finished because binder died");
-                    }
-                    endExternalVibrateLocked(
-                            new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
-                            /* continueExternalControl= */ false);
-                }
-            }
-        }
-
-        public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
-                    /* originalEffect= */ null, scale.scaleLevel, scale.adaptiveHapticsScale,
-                    callerInfo);
-        }
-
-        public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
-            return new VibrationStats.StatsInfo(
-                    externalVibration.getUid(),
-                    FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
-                    externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
-                    completionUptimeMillis);
-        }
-
-        @Override
-        boolean isRepeating() {
-            // We don't currently know if the external vibration is repeating, so we just use a
-            // heuristic based on the usage. Ideally this would be propagated in the
-            // ExternalVibration.
-            int usage = externalVibration.getVibrationAttributes().getUsage();
-            return usage == VibrationAttributes.USAGE_RINGTONE
-                    || usage == VibrationAttributes.USAGE_ALARM;
-        }
-    }
-
     /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
     @VisibleForTesting
     public static class NativeWrapper {
@@ -1894,7 +1791,7 @@
                     new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
         }
 
-        synchronized void record(Vibration.DebugInfo info) {
+        synchronized void record(DebugInfo info) {
             GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
                     mRecentVibrations.add(new VibrationRecord(info));
             if (droppedRecord != null) {
@@ -1951,25 +1848,25 @@
     }
 
     /**
-     * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
-     * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
+     * Record for a single {@link DebugInfo}, that can be grouped by usage and aggregated by UID,
+     * {@link VibrationAttributes} and {@link CombinedVibration}.
      */
     private static final class VibrationRecord
             implements GroupedAggregatedLogRecords.SingleLogRecord {
-        private final Vibration.DebugInfo mInfo;
+        private final DebugInfo mInfo;
 
-        VibrationRecord(Vibration.DebugInfo info) {
+        VibrationRecord(DebugInfo info) {
             mInfo = info;
         }
 
         @Override
         public int getGroupKey() {
-            return mInfo.mCallerInfo.attrs.getUsage();
+            return mInfo.getCallerInfo().attrs.getUsage();
         }
 
         @Override
         public long getCreateUptimeMs() {
-            return mInfo.mCreateTime;
+            return mInfo.getCreateUptimeMillis();
         }
 
         @Override
@@ -1977,10 +1874,10 @@
             if (!(record instanceof VibrationRecord)) {
                 return false;
             }
-            Vibration.DebugInfo info = ((VibrationRecord) record).mInfo;
-            return mInfo.mCallerInfo.uid == info.mCallerInfo.uid
-                    && Objects.equals(mInfo.mCallerInfo.attrs, info.mCallerInfo.attrs)
-                    && Objects.equals(mInfo.mPlayedEffect, info.mPlayedEffect);
+            DebugInfo info = ((VibrationRecord) record).mInfo;
+            return mInfo.getCallerInfo().uid == info.getCallerInfo().uid
+                    && Objects.equals(mInfo.getCallerInfo().attrs, info.getCallerInfo().attrs)
+                    && Objects.equals(mInfo.getDumpAggregationKey(), info.getDumpAggregationKey());
         }
 
         @Override
@@ -2030,7 +1927,8 @@
                 setExternalControl(false, mCurrentExternalVibration.stats);
             }
             // The external control was turned off, end it and report metrics right away.
-            endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+            endVibrationLocked(mCurrentExternalVibration, vibrationEndInfo,
+                    /* shouldWriteStats= */ true);
             mCurrentExternalVibration = null;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -2090,17 +1988,18 @@
         @Override
         public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
             // Create Vibration.Stats as close to the received request as possible, for tracking.
-            ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+            ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib);
             // Mute the request until we run all the checks and accept the vibration.
-            vibHolder.muteScale();
+            externalVibration.muteScale();
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
 
             synchronized (mLock) {
                 if (!hasExternalControlCapability()) {
-                    endVibrationAndWriteStatsLocked(vibHolder,
-                            new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED));
-                    return vibHolder.scale;
+                    endVibrationLocked(externalVibration,
+                            new Vibration.EndInfo(Status.IGNORED_UNSUPPORTED),
+                            /* shouldWriteStats= */ true);
+                    return externalVibration.getScale();
                 }
 
                 if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
@@ -2109,44 +2008,46 @@
                     Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                             + " tried to play externally controlled vibration"
                             + " without VIBRATE permission, ignoring.");
-                    endVibrationAndWriteStatsLocked(vibHolder,
-                            new Vibration.EndInfo(Vibration.Status.IGNORED_MISSING_PERMISSION));
-                    return vibHolder.scale;
+                    endVibrationLocked(externalVibration,
+                            new Vibration.EndInfo(Status.IGNORED_MISSING_PERMISSION),
+                            /* shouldWriteStats= */ true);
+                    return externalVibration.getScale();
                 }
 
                 Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
-                        vibHolder.callerInfo);
+                        externalVibration.callerInfo);
 
                 if (vibrationEndInfo == null
                         && mCurrentExternalVibration != null
                         && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                     // We are already playing this external vibration, so we can return the same
                     // scale calculated in the previous call to this method.
-                    return mCurrentExternalVibration.scale;
+                    return mCurrentExternalVibration.getScale();
                 }
 
                 if (vibrationEndInfo == null) {
                     // Check if ongoing vibration is more important than this vibration.
-                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vibHolder);
+                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration);
                 }
 
                 if (vibrationEndInfo != null) {
-                    endVibrationAndWriteStatsLocked(vibHolder, vibrationEndInfo);
-                    return vibHolder.scale;
+                    endVibrationLocked(externalVibration, vibrationEndInfo,
+                            /* shouldWriteStats= */ true);
+                    return externalVibration.getScale();
                 }
 
                 if (mCurrentExternalVibration == null) {
                     // If we're not under external control right now, then cancel any normal
                     // vibration that may be playing and ready the vibrator for external control.
                     if (mCurrentVibration != null) {
-                        vibHolder.stats.reportInterruptedAnotherVibration(
+                        externalVibration.stats.reportInterruptedAnotherVibration(
                                 mCurrentVibration.getVibration().callerInfo);
                         clearNextVibrationLocked(
-                                new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
-                                        vibHolder.callerInfo));
+                                new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL,
+                                        externalVibration.callerInfo));
                         mCurrentVibration.notifyCancelled(
-                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                        vibHolder.callerInfo),
+                                new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+                                        externalVibration.callerInfo),
                                 /* immediate= */ true);
                         waitForCompletion = true;
                     }
@@ -2160,12 +2061,12 @@
                     // Note that this doesn't support multiple concurrent external controls, as we
                     // would need to mute the old one still if it came from a different controller.
                     alreadyUnderExternalControl = true;
-                    mCurrentExternalVibration.mute();
-                    vibHolder.stats.reportInterruptedAnotherVibration(
+                    mCurrentExternalVibration.notifyEnded();
+                    externalVibration.stats.reportInterruptedAnotherVibration(
                             mCurrentExternalVibration.callerInfo);
                     endExternalVibrateLocked(
-                            new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                    vibHolder.callerInfo),
+                            new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+                                    externalVibration.callerInfo),
                             /* continueExternalControl= */ true);
                 }
 
@@ -2177,9 +2078,9 @@
                     mVibrationSettings.update();
                 }
 
-                mCurrentExternalVibration = vibHolder;
-                vibHolder.linkToDeath();
-                vibHolder.scale(mVibrationScaler, attrs.getUsage());
+                mCurrentExternalVibration = externalVibration;
+                externalVibration.linkToDeath(this::onExternalVibrationBinderDied);
+                externalVibration.scale(mVibrationScaler, attrs.getUsage());
             }
 
             if (waitForCompletion) {
@@ -2188,27 +2089,27 @@
                     synchronized (mLock) {
                         // Trigger endExternalVibrateLocked to unlink to death recipient.
                         endExternalVibrateLocked(
-                                new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
+                                new Vibration.EndInfo(Status.IGNORED_ERROR_CANCELLING),
                                 /* continueExternalControl= */ false);
                         // Mute the request, vibration will be ignored.
-                        vibHolder.muteScale();
+                        externalVibration.muteScale();
                     }
-                    return vibHolder.scale;
+                    return externalVibration.getScale();
                 }
             }
             if (!alreadyUnderExternalControl) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibrator going under external control.");
                 }
-                setExternalControl(true, vibHolder.stats);
+                setExternalControl(true, externalVibration.stats);
             }
             if (DEBUG) {
                 Slog.d(TAG, "Playing external vibration: " + vib);
             }
             // Vibrator will start receiving data from external channels after this point.
             // Report current time as the vibration start time, for debugging.
-            vibHolder.stats.reportStarted();
-            return vibHolder.scale;
+            externalVibration.stats.reportStarted();
+            return externalVibration.getScale();
         }
 
         @Override
@@ -2220,7 +2121,7 @@
                         Slog.d(TAG, "Stopping external vibration: " + vib);
                     }
                     endExternalVibrateLocked(
-                            new Vibration.EndInfo(Vibration.Status.FINISHED),
+                            new Vibration.EndInfo(Status.FINISHED),
                             /* continueExternalControl= */ false);
                 }
             }
@@ -2234,6 +2135,19 @@
             }
             return false;
         }
+
+        private void onExternalVibrationBinderDied() {
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "External vibration finished because binder died");
+                    }
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED),
+                            /* continueExternalControl= */ false);
+                }
+            }
+        }
     }
 
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ba2594a..f53dda6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.avoidRebindingIntentionallyDisconnectedWallpaper;
 import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.offloadColorExtraction;
 
@@ -897,6 +898,12 @@
                     return;
                 }
 
+                if (avoidRebindingIntentionallyDisconnectedWallpaper()
+                        && mWallpaper.connection == null) {
+                    Slog.w(TAG, "Trying to reset an intentionally disconnected wallpaper!");
+                    return;
+                }
+
                 if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
                     Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
                             + ", reverting to built-in wallpaper!");
@@ -1066,6 +1073,13 @@
                 if (mWallpaper.wallpaperUpdating) {
                     return;
                 }
+
+                if (avoidRebindingIntentionallyDisconnectedWallpaper()
+                        && mWallpaper.connection == null) {
+                    Slog.w(TAG, "Trying to rebind an intentionally disconnected wallpaper!");
+                    return;
+                }
+
                 final ComponentName wpService = mWallpaper.wallpaperComponent;
                 // The broadcast of package update could be delayed after service disconnected. Try
                 // to re-bind the service for 10 seconds.
diff --git a/services/core/java/com/android/server/wm/ActionChain.java b/services/core/java/com/android/server/wm/ActionChain.java
new file mode 100644
index 0000000..d63044a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActionChain.java
@@ -0,0 +1,240 @@
+/*
+ * 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.wm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Represents a chain of WM actions where each action is "caused by" the prior action (except the
+ * first one of course). A whole chain is associated with one Transition (in fact, the purpose
+ * of this object is to communicate, to all callees, which transition they are part of).
+ *
+ * A single action is defined as "one logical thing requested of WM". This usually corresponds to
+ * each ingress-point into the process. For example, when starting an activity:
+ *   * the first action is to pause the current/top activity.
+ *       At this point, control leaves the process while the activity pauses.
+ *   * Then WM receives completePause (a new ingress). This is a new action that gets linked
+ *       to the prior action. This action involves resuming the next activity, at which point,
+ *       control leaves the process again.
+ *   * Eventually, when everything is done, we will have formed a chain of actions.
+ *
+ * We don't technically need to hold onto each prior action in the chain once a new action has
+ * been linked to the same transition; however, keeping the whole chain enables improved
+ * debugging and the ability to detect anomalies.
+ */
+public class ActionChain {
+    private static final String TAG = "TransitionChain";
+
+    /**
+     * Normal link type. This means the action was expected and is properly linked to the
+     * current chain.
+     */
+    static final int TYPE_NORMAL = 0;
+
+    /**
+     * This is the "default" link. It means we haven't done anything to properly track this case
+     * so it may or may not be correct. It represents the behavior as if there was no tracking.
+     *
+     * Any type that has "default" behavior uses the global "collecting transition" if it exists,
+     * otherwise it doesn't use any transition.
+     */
+    static final int TYPE_DEFAULT = 1;
+
+    /**
+     * This means the action was performed via a legacy code-path. These should be removed
+     * eventually. This will have the "default" behavior.
+     */
+    static final int TYPE_LEGACY = 2;
+
+    /** This is for a test. */
+    static final int TYPE_TEST = 3;
+
+    /** This is finishing a transition. Collection isn't supported during this. */
+    static final int TYPE_FINISH = 4;
+
+    /**
+     * Something unexpected happened so this action was started to recover from the unexpected
+     * state. This means that a "real" chain-link couldn't be determined. For now, the behavior of
+     * this is the same as "default".
+     */
+    static final int TYPE_FAILSAFE = 5;
+
+    /**
+     * Types of chain links (ie. how is this action associated with the chain it is linked to).
+     * @hide
+     */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_NORMAL,
+            TYPE_DEFAULT,
+            TYPE_LEGACY,
+            TYPE_TEST,
+            TYPE_FINISH,
+            TYPE_FAILSAFE
+    })
+    public @interface LinkType {}
+
+    /** Identifies the entry-point of this action. */
+    @NonNull
+    final String mSource;
+
+    /** Reference to ATMS. TEMPORARY! ONLY USE THIS WHEN tracker_plumbing flag is DISABLED! */
+    @Nullable
+    ActivityTaskManagerService mTmpAtm;
+
+    /** The transition that this chain's changes belong to. */
+    @Nullable
+    Transition mTransition;
+
+    /** The previous action in the chain. */
+    @Nullable
+    ActionChain mPrevious = null;
+
+    /** Classification of how this action is connected to the chain. */
+    @LinkType int mType = TYPE_NORMAL;
+
+    /** When this Action started. */
+    long mCreateTimeMs;
+
+    private ActionChain(String source, @LinkType int type, Transition transit) {
+        mSource = source;
+        mCreateTimeMs = System.currentTimeMillis();
+        mType = type;
+        mTransition = transit;
+        if (mTransition != null) {
+            mTransition.recordChain(this);
+        }
+    }
+
+    private Transition getTransition() {
+        if (!Flags.transitTrackerPlumbing()) {
+            return mTmpAtm.getTransitionController().getCollectingTransition();
+        }
+        return mTransition;
+    }
+
+    boolean isFinishing() {
+        return mType == TYPE_FINISH;
+    }
+
+    /**
+     * Some common checks to determine (and report) whether this chain has a collecting transition.
+     */
+    private boolean expectCollecting() {
+        final Transition transition = getTransition();
+        if (transition == null) {
+            Slog.e(TAG, "Can't collect into a chain with no transition");
+            return false;
+        }
+        if (isFinishing()) {
+            Slog.e(TAG, "Trying to collect into a finished transition");
+            return false;
+        }
+        if (transition.mController.getCollectingTransition() != mTransition) {
+            Slog.e(TAG, "Mismatch between current collecting ("
+                    + transition.mController.getCollectingTransition() + ") and chain ("
+                    + transition + ")");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Helper to collect a container into the associated transition. This will automatically do
+     * nothing if the chain isn't associated with a collecting transition.
+     */
+    void collect(@NonNull WindowContainer wc) {
+        if (!wc.mTransitionController.isShellTransitionsEnabled()) return;
+        if (!expectCollecting()) return;
+        getTransition().collect(wc);
+    }
+
+    /**
+     * An interface for creating and tracking action chains.
+     */
+    static class Tracker {
+        private final ActivityTaskManagerService mAtm;
+
+        Tracker(ActivityTaskManagerService atm) {
+            mAtm = atm;
+        }
+
+        private ActionChain makeChain(String source, @LinkType int type, Transition transit) {
+            final ActionChain out = new ActionChain(source, type, transit);
+            if (!Flags.transitTrackerPlumbing()) {
+                out.mTmpAtm = mAtm;
+            }
+            return out;
+        }
+
+        private ActionChain makeChain(String source, @LinkType int type) {
+            return makeChain(source, type,
+                    mAtm.getTransitionController().getCollectingTransition());
+        }
+
+        /**
+         * Starts tracking a normal action.
+         * @see #TYPE_NORMAL
+         */
+        @NonNull
+        ActionChain start(String source, Transition transit) {
+            return makeChain(source, TYPE_NORMAL, transit);
+        }
+
+        /** @see #TYPE_DEFAULT */
+        @NonNull
+        ActionChain startDefault(String source) {
+            return makeChain(source, TYPE_DEFAULT);
+        }
+
+        /**
+         * Starts tracking an action that finishes a transition.
+         * @see #TYPE_NORMAL
+         */
+        @NonNull
+        ActionChain startFinish(String source, Transition finishTransit) {
+            return makeChain(source, TYPE_FINISH, finishTransit);
+        }
+
+        /** @see #TYPE_LEGACY */
+        @NonNull
+        ActionChain startLegacy(String source) {
+            return makeChain(source, TYPE_LEGACY, null);
+        }
+
+        /** @see #TYPE_FAILSAFE */
+        @NonNull
+        ActionChain startFailsafe(String source) {
+            return makeChain(source, TYPE_FAILSAFE);
+        }
+    }
+
+    /** Helpers for usage in tests. */
+    @NonNull
+    static ActionChain test() {
+        return new ActionChain("test", TYPE_TEST, null /* transition */);
+    }
+
+    @NonNull
+    static ActionChain testFinish(Transition toFinish) {
+        return new ActionChain("test", TYPE_FINISH, toFinish);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index e27b2be..c1e859d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -864,8 +864,9 @@
                 if (transition != null) {
                     if (changed) {
                         // Always set as scene transition because it expects to be a jump-cut.
-                        transition.setOverrideAnimation(TransitionInfo.AnimationOptions
-                                .makeSceneTransitionAnimOptions(), null, null);
+                        transition.setOverrideAnimation(
+                                TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), r,
+                                null, null);
                         r.mTransitionController.requestStartTransition(transition,
                                 null /*startTask */, null /* remoteTransition */,
                                 null /* displayChange */);
@@ -910,8 +911,9 @@
                                 && under.returningOptions.getAnimationType()
                                         == ANIM_SCENE_TRANSITION) {
                             // Pass along the scene-transition animation-type
-                            transition.setOverrideAnimation(TransitionInfo.AnimationOptions
-                                    .makeSceneTransitionAnimOptions(), null, null);
+                            transition.setOverrideAnimation(TransitionInfo
+                                            .AnimationOptions.makeSceneTransitionAnimOptions(), r,
+                                    null, null);
                         }
                     } else {
                         transition.abort();
@@ -1508,7 +1510,7 @@
                         r.mOverrideTaskTransition);
                 r.mTransitionController.setOverrideAnimation(
                         TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
-                                enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
+                                enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), r,
                         null /* startCallback */, null /* finishCallback */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 47d4740..235a211 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -353,6 +353,7 @@
 import android.window.SplashScreenView;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
+import android.window.TransitionInfo;
 import android.window.TransitionInfo.AnimationOptions;
 import android.window.WindowContainerToken;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -2619,7 +2620,8 @@
                 || mStartingWindow == null
                 || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
                 // skip copy splash screen to client if it was resized
-                || (mStartingData != null && mStartingData.mResizedFromTransfer)) {
+                || (mStartingData != null && mStartingData.mResizedFromTransfer)
+                || isRelaunching()) {
             return false;
         }
         if (isTransferringSplashScreen()) {
@@ -5033,7 +5035,8 @@
                 // controller but don't clear the animation information from the options since they
                 // need to be sent to the animating activity.
                 mTransitionController.setOverrideAnimation(
-                        AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
+                        TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), this,
+                        null, null);
                 return;
             }
             applyOptionsAnimation(mPendingOptions, intent);
@@ -5156,7 +5159,8 @@
         }
 
         if (options != null) {
-            mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
+            mTransitionController.setOverrideAnimation(options, this, startCallback,
+                    finishCallback);
         }
     }
 
@@ -5889,6 +5893,7 @@
                     mAtmService.updateBatteryStats(this, false);
                 }
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+                idle = false;
                 // Fall through.
             case DESTROYING:
                 if (app != null && !app.hasActivities()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6479111..bf18a43 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1945,7 +1945,7 @@
                 && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
                 && balVerdict.allows()) {
             mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
-                    sourceRecord, "launch-into-pip");
+                    sourceRecord, "launch-into-pip", null /* bounds */);
         }
 
         mSupervisor.getBackgroundActivityLaunchController()
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0f108c5..f5476f2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -795,6 +795,7 @@
     WindowOrganizerController mWindowOrganizerController;
     TaskOrganizerController mTaskOrganizerController;
     TaskFragmentOrganizerController mTaskFragmentOrganizerController;
+    ActionChain.Tracker mChainTracker;
 
     @Nullable
     private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -869,6 +870,7 @@
         mInternal = new LocalService();
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
         mWindowOrganizerController = new WindowOrganizerController(this);
+        mChainTracker = new ActionChain.Tracker(this);
         mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
         mTaskFragmentOrganizerController =
                 mWindowOrganizerController.mTaskFragmentOrganizerController;
@@ -3794,9 +3796,22 @@
                         r.shortComponentName, Boolean.toString(isAutoEnter));
                 r.setPictureInPictureParams(params);
                 r.mAutoEnteringPip = isAutoEnter;
-                mRootWindowContainer.moveActivityToPinnedRootTask(r,
-                        null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
-                        transition);
+
+                if (transition != null) {
+                    mRootWindowContainer.moveActivityToPinnedRootTaskAndRequestStart(r,
+                            "enterPictureInPictureMode");
+                } else if (getTransitionController().isCollecting()
+                        || !getTransitionController().isShellTransitionsEnabled()) {
+                    mRootWindowContainer.moveActivityToPinnedRootTask(r,
+                            null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+                            null /* bounds */);
+                } else {
+                    // Need to make a transition just for this. This shouldn't really happen
+                    // though because if transition == null, we should be part of an existing one.
+                    getTransitionController().createTransition(TRANSIT_PIP);
+                    mRootWindowContainer.moveActivityToPinnedRootTaskAndRequestStart(r,
+                            "enterPictureInPictureMode");
+                }
                 // Continue the pausing process after entering pip.
                 if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
                     r.getTask().schedulePauseActivity(r, false /* userLeaving */,
@@ -4755,6 +4770,10 @@
         }
     }
 
+    boolean mayBeLaunchingApp() {
+        return (mPowerModeReasons & POWER_MODE_REASON_START_ACTIVITY) != 0;
+    }
+
     void startPowerMode(@PowerModeReason int reason) {
         final int prevReasons = mPowerModeReasons;
         mPowerModeReasons |= reason;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1446c35..e90a2c9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -202,6 +202,12 @@
      */
     private static final int KILL_TASK_PROCESSES_TIMEOUT_MS = 1000;
 
+    /**
+     * The delay to run idle check. It may give a chance to keep launch power mode if an activity
+     * is starting while the device is sleeping and then the device is unlocked in a short time.
+     */
+    private static final int IDLE_NOW_DELAY_WHILE_SLEEPING_MS = 100;
+
     private static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_TASK_MSG;
     private static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_TASK_MSG + 1;
     private static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_TASK_MSG + 2;
@@ -2252,7 +2258,9 @@
     final void scheduleIdle() {
         if (!mHandler.hasMessages(IDLE_NOW_MSG)) {
             if (DEBUG_IDLE) Slog.d(TAG_IDLE, "scheduleIdle: Callers=" + Debug.getCallers(4));
-            mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+            final long delayMs = mService.isSleepingLocked() && mService.mayBeLaunchingApp()
+                    ? IDLE_NOW_DELAY_WHILE_SLEEPING_MS : 0;
+            mHandler.sendEmptyMessageDelayed(IDLE_NOW_MSG, delayMs);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 87867f6..3710f7f 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -574,10 +574,15 @@
 
     private void scheduleAnimation(@NonNull AnimationHandler.ScheduleAnimationBuilder builder) {
         mPendingAnimation = builder.build();
-        mWindowManagerService.mWindowPlacerLocked.requestTraversal();
-        if (mShowWallpaper) {
-            mWindowManagerService.getDefaultDisplayContentLocked().mWallpaperController
-                    .adjustWallpaperWindows();
+        if (mAnimationHandler.mOpenAnimAdaptor != null
+                && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null) {
+            startAnimation();
+        } else {
+            mWindowManagerService.mWindowPlacerLocked.requestTraversal();
+            if (mShowWallpaper) {
+                mWindowManagerService.getDefaultDisplayContentLocked().mWallpaperController
+                        .adjustWallpaperWindows();
+            }
         }
     }
 
@@ -1203,7 +1208,7 @@
         }
 
         void markWindowHasDrawn(ActivityRecord activity) {
-            if (!mComposed || mWaitTransition || mOpenAnimAdaptor.mPreparedOpenTransition == null
+            if (!mComposed || mWaitTransition
                     || mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
                 return;
             }
@@ -1215,6 +1220,10 @@
                 }
                 allWindowDrawn &= next.mAppWindowDrawn;
             }
+            // Do not remove until transition ready.
+            if (!activity.isVisible()) {
+                return;
+            }
             if (allWindowDrawn) {
                 mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
             }
@@ -1289,6 +1298,14 @@
             if (mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
                 return;
             }
+            boolean allWindowDrawn = true;
+            for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+                final BackWindowAnimationAdaptor next = mOpenAnimAdaptor.mAdaptors[i];
+                allWindowDrawn &= next.mAppWindowDrawn;
+            }
+            if (!allWindowDrawn) {
+                return;
+            }
             final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
             if (startingSurface != null && startingSurface.isValid()) {
                 startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
@@ -1820,8 +1837,10 @@
                             mNavigationMonitor.cancelBackNavigating("cancelAnimation");
                             mBackAnimationAdapter.getRunner().onAnimationCancelled();
                         } else {
-                            mBackAnimationAdapter.getRunner().onAnimationStart(
-                                    targets, null, null, callback);
+                            mBackAnimationAdapter.getRunner().onAnimationStart(targets,
+                                    mOpenAnimAdaptor.mPreparedOpenTransition != null
+                                            ? mOpenAnimAdaptor.mPreparedOpenTransition.getToken()
+                                            : null, callback);
                         }
                     } catch (RemoteException e) {
                         e.printStackTrace();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a6d9659..866dcd5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2032,33 +2032,39 @@
                 onTop);
     }
 
-    void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
-            @Nullable ActivityRecord launchIntoPipHostActivity, String reason) {
-        moveActivityToPinnedRootTask(r, launchIntoPipHostActivity, reason, null /* transition */);
+    /** Wrapper/Helper for tests */
+    void moveActivityToPinnedRootTask(@NonNull ActivityRecord r, String reason) {
+        Transition newTransit = (r.mTransitionController.isCollecting()
+                || !r.mTransitionController.isShellTransitionsEnabled())
+                ? null : r.mTransitionController.createTransition(TRANSIT_PIP);
+        moveActivityToPinnedRootTaskInner(r, null /* launchIntoPipHostActivity */, reason,
+                null /* bounds */, newTransit != null);
     }
 
     void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
             @Nullable ActivityRecord launchIntoPipHostActivity, String reason,
-            @Nullable Transition transition) {
-        moveActivityToPinnedRootTask(r, launchIntoPipHostActivity, reason, transition,
-                null /* bounds */);
+            @Nullable Rect bounds) {
+        moveActivityToPinnedRootTaskInner(r, launchIntoPipHostActivity, reason, bounds,
+                false /* requestStart */);
     }
 
-    void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
+    /**
+     * Moves activity to pinned in the provided transition and also requests start on that
+     * Transition at an appropriate time.
+     */
+    void moveActivityToPinnedRootTaskAndRequestStart(@NonNull ActivityRecord r, String reason) {
+        moveActivityToPinnedRootTaskInner(r, null /* launchIntoPipHostActivity */, reason,
+                null /* bounds */, true /* requestStart */);
+    }
+
+    private void moveActivityToPinnedRootTaskInner(@NonNull ActivityRecord r,
             @Nullable ActivityRecord launchIntoPipHostActivity, String reason,
-            @Nullable Transition transition, @Nullable Rect bounds) {
+            @Nullable Rect bounds, boolean requestStart) {
         final TaskDisplayArea taskDisplayArea = r.getDisplayArea();
         final Task task = r.getTask();
         final Task rootTask;
 
-        Transition newTransition = transition;
-        // Create a transition now (if not provided) to collect the current pinned Task dismiss.
-        // Only do the create here as the Task (trigger) to enter PIP is not ready yet.
         final TransitionController transitionController = task.mTransitionController;
-        if (newTransition == null && !transitionController.isCollecting()
-                && transitionController.getTransitionPlayer() != null) {
-            newTransition = transitionController.createTransition(TRANSIT_PIP);
-        }
 
         transitionController.deferTransitionReady();
         Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip");
@@ -2273,14 +2279,16 @@
             }
         }
 
-        if (newTransition != null) {
+        // can be null (for now) if shell transitions are disabled or inactive at this time
+        final Transition transit = transitionController.getCollectingTransition();
+        if (requestStart && transit != null) {
             // Request at end since we want task-organizer events from ensureActivitiesVisible
             // to be recognized.
-            transitionController.requestStartTransition(newTransition, rootTask,
+            transitionController.requestStartTransition(transit, rootTask,
                     null /* remoteTransition */, null /* displayChange */);
             // A new transition was created just for this operations. Since the operation is
             // complete, mark it as ready.
-            newTransition.setReady(rootTask, true /* ready */);
+            transit.setReady(rootTask, true /* ready */);
         }
 
         resumeFocusedTasksTopActivities();
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 0f9c001..52994c7 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -31,6 +31,8 @@
 import android.view.WindowManager;
 import android.window.TaskSnapshot;
 
+import com.android.window.flags.Flags;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -150,6 +152,9 @@
                 if (mOpenActivities.isEmpty()) {
                     return false;
                 }
+                if (Flags.alwaysCaptureActivitySnapshot()) {
+                    return true;
+                }
                 for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
                     if (!mOpenActivities.get(i).mOptInOnBackInvoked) {
                         return false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e25db7e..7f6dc84 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -181,7 +181,7 @@
     final @TransitionType int mType;
     private int mSyncId = -1;
     private @TransitionFlags int mFlags;
-    private final TransitionController mController;
+    final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
     private final Token mToken;
 
@@ -329,6 +329,9 @@
      */
     ArrayList<ActivityRecord> mConfigAtEndActivities = null;
 
+    /** The current head of the chain of actions related to this transition. */
+    ActionChain mChainHead = null;
+
     @VisibleForTesting
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
@@ -950,10 +953,13 @@
      * Set animation options for collecting transition by ActivityRecord.
      * @param options AnimationOptions captured from ActivityOptions
      */
-    void setOverrideAnimation(@Nullable AnimationOptions options,
+    void setOverrideAnimation(@Nullable AnimationOptions options, @NonNull ActivityRecord r,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
         if (!isCollecting()) return;
         mOverrideOptions = options;
+        if (mOverrideOptions != null) {
+            mOverrideOptions.setUserId(r.mUserId);
+        }
         sendRemoteCallback(mClientAnimationStartCallback);
         mClientAnimationStartCallback = startCallback;
         mClientAnimationFinishCallback = finishCallback;
@@ -1051,9 +1057,8 @@
      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
      * Additionally, this gives shell the ability to better deal with merged transitions.
      */
-    private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
-        // usually only size 1
-        final ArraySet<DisplayContent> displays = new ArraySet<>();
+    private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info,
+            DisplayContent[] participantDisplays) {
         for (int i = mTargets.size() - 1; i >= 0; --i) {
             final WindowContainer<?> target = mTargets.get(i).mContainer;
             if (target.getParent() == null) continue;
@@ -1065,7 +1070,6 @@
             t.setCornerRadius(targetLeash, 0);
             t.setShadowRadius(targetLeash, 0);
             t.setAlpha(targetLeash, 1);
-            displays.add(target.getDisplayContent());
             // For config-at-end, the end-transform will be reset after the config is actually
             // applied in the client (since the transform depends on config). The other properties
             // remain here because shell might want to persistently override them.
@@ -1079,9 +1083,8 @@
         }
         // Need to update layers on involved displays since they were all paused while
         // the animation played. This puts the layers back into the correct order.
-        for (int i = displays.size() - 1; i >= 0; --i) {
-            if (displays.valueAt(i) == null) continue;
-            assignLayers(displays.valueAt(i), t);
+        for (int i = participantDisplays.length - 1; i >= 0; --i) {
+            assignLayers(participantDisplays[i], t);
         }
 
         for (int i = 0; i < info.getRootCount(); ++i) {
@@ -1207,10 +1210,14 @@
      * The transition has finished animating and is ready to finalize WM state. This should not
      * be called directly; use {@link TransitionController#finishTransition} instead.
      */
-    void finishTransition() {
+    void finishTransition(@NonNull ActionChain chain) {
         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
             asyncTraceEnd(System.identityHashCode(this));
         }
+        if (!chain.isFinishing()) {
+            throw new IllegalStateException("Can't finish on a non-finishing transition "
+                    + chain.mTransition);
+        }
         mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
         mController.mLoggerHandler.post(mLogger::logOnFinish);
         mController.mTransitionTracer.logFinishedTransition(this);
@@ -1790,6 +1797,8 @@
         mController.moveToPlaying(this);
 
         // Repopulate the displays based on the resolved targets.
+        final DisplayContent[] participantDisplays = mTargetDisplays.toArray(
+                new DisplayContent[mTargetDisplays.size()]);
         mTargetDisplays.clear();
         for (int i = 0; i < info.getRootCount(); ++i) {
             final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
@@ -1883,7 +1892,9 @@
                 controller.setupStartTransaction(transaction);
             }
         }
-        buildFinishTransaction(mFinishTransaction, info);
+        // Use participant displays here (rather than just targets) because it's possible for
+        // there to be order changes between non-top tasks in an otherwise no-op transition.
+        buildFinishTransaction(mFinishTransaction, info, participantDisplays);
         mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         buildCleanupTransaction(mCleanupTransaction, info);
         if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
@@ -2163,7 +2174,7 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(this);
+        mController.finishTransition(mController.mAtm.mChainTracker.startFinish("clean-up", this));
     }
 
     private void cleanUpInternal() {
@@ -2811,7 +2822,7 @@
                 }
             }
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
-                    "Transition Root: " + leashReference.getName())
+                            "Transition Root: " + leashReference.getName())
                     .setCallsite("Transition.calculateTransitionRoots").build();
             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
             // Update layers to start transaction because we prevent assignment during collect, so
@@ -2982,7 +2993,8 @@
                         // Create the options based on this change's custom animations and layout
                         // parameters
                         animOptions = getOptions(activityRecord /* customAnimActivity */,
-                                                 activityRecord /* animLpActivity */);
+                                activityRecord /* animLpActivity */);
+                        animOptions.setUserId(activityRecord.mUserId);
                         if (!change.hasFlags(FLAG_TRANSLUCENT)) {
                             // If this change is not translucent, its options are going to be
                             // inherited by the changes below
@@ -2992,6 +3004,7 @@
                 } else if (activityRecord != null && animOptionsForActivityTransition != null) {
                     // Use the same options from the top activity for all the activities
                     animOptions = animOptionsForActivityTransition;
+                    animOptions.setUserId(activityRecord.mUserId);
                 } else if (Flags.activityEmbeddingOverlayPresentationFlag()
                         && isEmbeddedTaskFragment) {
                     final TaskFragmentAnimationParams params = taskFragment.getAnimationParams();
@@ -3004,6 +3017,7 @@
                                 params.getOpenAnimationResId(), params.getChangeAnimationResId(),
                                 params.getCloseAnimationResId(), 0 /* backgroundColor */,
                                 false /* overrideTaskTransition */);
+                        animOptions.setUserId(taskFragment.getTask().mUserId);
                     }
                 }
                 if (animOptions != null) {
@@ -3067,6 +3081,9 @@
                     animOptions);
             animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
                     animOptions);
+            if (animOptions != null) {
+                animOptions.setUserId(customAnimActivity.mUserId);
+            }
         }
 
         // Layout parameters
@@ -3080,10 +3097,12 @@
             // are running an app starting animation, in which case we don't want the app to be
             // able to change its animation directly.
             if (animOptions != null) {
+                animOptions.setUserId(animLpActivity.mUserId);
                 animOptions.addOptionsFromLayoutParameters(animLp);
             } else {
                 animOptions = TransitionInfo.AnimationOptions
                         .makeAnimOptionsFromLayoutParameters(animLp);
+                animOptions.setUserId(animLpActivity.mUserId);
             }
         }
         return animOptions;
@@ -3109,6 +3128,7 @@
             if (animOptions == null) {
                 animOptions = TransitionInfo.AnimationOptions
                         .makeCommonAnimOptions(activity.packageName);
+                animOptions.setUserId(activity.mUserId);
             }
             animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
                     customAnim.mExitAnim, customAnim.mBackgroundColor);
@@ -3237,7 +3257,7 @@
         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
                 w -> w.getRemoteAnimationDefinition() != null
-                    && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+                        && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
         if (result != null) {
             return result;
         }
@@ -3284,7 +3304,7 @@
     private void validateKeyguardOcclusion() {
         if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
             mController.mStateValidators.add(
-                mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+                    mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
         }
     }
 
@@ -3379,6 +3399,11 @@
         return false;
     }
 
+    void recordChain(@NonNull ActionChain chain) {
+        chain.mPrevious = mChainHead;
+        mChainHead = chain;
+    }
+
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
@@ -3888,9 +3913,9 @@
 
         /** @return true if all tracked subtrees are ready. */
         boolean allReady() {
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
-                    + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
-                    groupsToString());
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    " allReady query: used=%b " + "override=%b defer=%d states=[%s]", mUsed,
+                    mReadyOverride, mDeferReadyDepth, groupsToString());
             // If the readiness has never been touched, mUsed will be false. We never want to
             // consider a transition ready if nothing has been reported on it.
             if (!mUsed) return false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 9bbf102..1d2a605 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -52,8 +52,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.server.FgThread;
 import com.android.window.flags.Flags;
 
@@ -880,10 +880,10 @@
     }
 
     /** @see Transition#setOverrideAnimation */
-    void setOverrideAnimation(TransitionInfo.AnimationOptions options,
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options, ActivityRecord r,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
         if (mCollectingTransition == null) return;
-        mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
+        mCollectingTransition.setOverrideAnimation(options, r, startCallback, finishCallback);
     }
 
     void setNoAnimation(WindowContainer wc) {
@@ -921,7 +921,12 @@
     }
 
     /** @see Transition#finishTransition */
-    void finishTransition(Transition record) {
+    void finishTransition(@NonNull ActionChain chain) {
+        if (!chain.isFinishing()) {
+            throw new IllegalStateException("Can't finish on a non-finishing transition "
+                    + chain.mTransition);
+        }
+        final Transition record = chain.mTransition;
         // It is usually a no-op but make sure that the metric consumer is removed.
         mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
         // It is a no-op if the transition did not change the display.
@@ -937,7 +942,7 @@
             mTrackCount = 0;
         }
         updateRunningRemoteAnimation(record, false /* isPlaying */);
-        record.finishTransition();
+        record.finishTransition(chain);
         for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
             final WindowState w = mAnimatingExitWindows.get(i);
             if (w.mAnimatingExit && w.mHasSurface && !w.inTransition()) {
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 9b868be..5f3c558 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -112,11 +112,16 @@
         final SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
 
         for (int i = mOverlays.size() - 1; i >= 0; i--) {
-           SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
-           if (l.getSurfaceControl().isSameSurface(p.getSurfaceControl())) {
-               mOverlays.remove(i);
-               t.reparent(l.getSurfaceControl(), null);
-               l.release();
+            SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+            SurfaceControl overlaySurfaceControl = l.getSurfaceControl();
+            if (overlaySurfaceControl == null) {
+                // Remove the overlay if the surfacepackage was released. Ownership
+                // is shared, so this may happen.
+                mOverlays.remove(i);
+            } else if (overlaySurfaceControl.isSameSurface(p.getSurfaceControl())) {
+                mOverlays.remove(i);
+                t.reparent(l.getSurfaceControl(), null);
+                l.release();
            }
         }
         t.apply();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9b55ed2..979b3a5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8949,7 +8949,7 @@
         final ActivityRecord touchedApp = t.getActivityRecord();
         if (touchedApp != null && touchedApp.getTask() != null) {
             final ActivityRecord top = touchedApp.getTask().topRunningActivity();
-            if (top != touchedApp && top.getTaskFragment().getBounds().contains(
+            if (top != null && top != touchedApp && top.getTaskFragment().getBounds().contains(
                     touchedApp.getTaskFragment().getBounds())) {
                 // This is a special case where the pointer-down-outside focus on an Activity that's
                 // entirely occluded by the task top running activity, this is possible if the
@@ -8977,9 +8977,8 @@
     }
 
     private boolean shouldDelayTouchOutside(InputTarget t) {
-        final WindowState w = t.getWindowState();
-        final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
-        final Task task = w != null ? w.getRootTask() : null;
+        final ActivityRecord activity = t.getActivityRecord();
+        final Task task = activity != null ? activity.getTask() : null;
 
         final boolean isInputTargetNotFocused =
                 mFocusedInputTarget != t && mFocusedInputTarget != null;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1e64ee..476443a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -223,7 +223,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller);
+                final ActionChain chain = mService.mChainTracker.startLegacy("applyTransactLegacy");
+                applyTransaction(t, -1 /*syncId*/, chain, caller);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -242,7 +243,8 @@
         try {
             synchronized (mGlobalLock) {
                 if (callback == null) {
-                    applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller);
+                    final ActionChain chain = mService.mChainTracker.startLegacy("applySyncLegacy");
+                    applyTransaction(t, -1 /* syncId*/, chain, caller);
                     return -1;
                 }
 
@@ -262,13 +264,15 @@
                 final int syncId = syncGroup.mSyncId;
                 if (mTransitionController.isShellTransitionsEnabled()) {
                     mTransitionController.startLegacySyncOrQueue(syncGroup, (deferred) -> {
-                        applyTransaction(t, syncId, null /* transition */, caller, deferred);
+                        applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+                                "applySyncLegacy"), caller, deferred);
                         setSyncReady(syncId);
                     });
                 } else {
                     if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
                         mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
-                        applyTransaction(t, syncId, null /*transition*/, caller);
+                        applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+                                "applySyncLegacy"), caller);
                         setSyncReady(syncId);
                     } else {
                         // Because the BLAST engine only supports one sync at a time, queue the
@@ -276,7 +280,8 @@
                         mService.mWindowManager.mSyncEngine.queueSyncSet(
                                 () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup),
                                 () -> {
-                                    applyTransaction(t, syncId, null /*transition*/, caller);
+                                    applyTransaction(t, syncId, mService.mChainTracker.startLegacy(
+                                            "applySyncLegacy"), caller);
                                     setSyncReady(syncId);
                                 });
                     }
@@ -313,7 +318,8 @@
                         throw new IllegalArgumentException("Can't use legacy transitions in"
                                 + " compatibility mode with no WCT.");
                     }
-                    applyTransaction(t, -1 /* syncId */, null, caller);
+                    applyTransaction(t, -1 /* syncId */,
+                            mService.mChainTracker.startLegacy("wrongLegacyTransit"), caller);
                     return null;
                 }
                 final WindowContainerTransaction wct =
@@ -334,10 +340,11 @@
                     nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
                             (deferred) -> {
+                                final ActionChain chain = mService.mChainTracker.start(
+                                        "startNewTransit", nextTransition);
                                 nextTransition.start();
                                 nextTransition.mLogger.mStartWCT = wct;
-                                applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
-                                        deferred);
+                                applyTransaction(wct, -1 /* syncId */, chain, caller, deferred);
                                 wctApplied.meet();
                                 if (needsSetReady) {
                                     setAllReadyIfNeeded(nextTransition, wct);
@@ -351,7 +358,9 @@
                     Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
                             + " means Shell took too long to respond to a request. WM State may be"
                             + " incorrect now, please file a bug");
-                    applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+                    final ActionChain chain = mService.mChainTracker.startFailsafe("startTransit");
+                    chain.mTransition = null;
+                    applyTransaction(wct, -1 /*syncId*/, chain, caller);
                     return transition.getToken();
                 }
                 // Currently, application of wct can span multiple looper loops (ie.
@@ -367,16 +376,20 @@
                 if (transition.shouldApplyOnDisplayThread()) {
                     mService.mH.post(() -> {
                         synchronized (mService.mGlobalLock) {
+                            final ActionChain chain = mService.mChainTracker.start(
+                                    "startTransit", transition);
                             transition.start();
-                            applyTransaction(wct, -1 /* syncId */, transition, caller);
+                            applyTransaction(wct, -1 /* syncId */, chain, caller);
                             if (wctApplied != null) {
                                 wctApplied.meet();
                             }
                         }
                     });
                 } else {
+                    final ActionChain chain = mService.mChainTracker.start("startTransit",
+                            transition);
                     transition.start();
-                    applyTransaction(wct, -1 /* syncId */, transition, caller);
+                    applyTransaction(wct, -1 /* syncId */, chain, caller);
                     if (wctApplied != null) {
                         wctApplied.meet();
                     }
@@ -475,7 +488,8 @@
                 dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */,
                         false /* isActivityEmbedding */);
                 syncId = startSyncWithOrganizer(callback);
-                applyTransaction(t, syncId, null /* transition */, caller);
+                applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"),
+                        caller);
                 setSyncReady(syncId);
             }
         } finally {
@@ -493,6 +507,8 @@
         try {
             synchronized (mGlobalLock) {
                 final Transition transition = Transition.fromBinder(transitionToken);
+                final ActionChain chain =
+                        mService.mChainTracker.startFinish("finishTransit", transition);
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
                 if (t != null) {
@@ -500,9 +516,9 @@
                     // changes of the transition participants will only set visible-requested
                     // and still let finishTransition handle the participants.
                     mTransitionController.mFinishingTransition = transition;
-                    applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition);
+                    applyTransaction(t, -1 /* syncId */, chain, caller);
                 }
-                mTransitionController.finishTransition(transition);
+                mTransitionController.finishTransition(chain);
                 mTransitionController.mFinishingTransition = null;
             }
         } finally {
@@ -537,9 +553,10 @@
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (mTransitionController.getTransitionPlayer() == null) {
+            if (!mTransitionController.isShellTransitionsEnabled()) {
                 // No need to worry about transition when Shell transition is not enabled.
-                applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
+                applyTransaction(wct, -1 /* syncId */,
+                        mService.mChainTracker.startLegacy("legacyTFTransact"), caller);
                 return;
             }
 
@@ -548,8 +565,8 @@
                 // Although there is an active sync, we want to apply the transaction now.
                 // TODO(b/232042367) Redesign the organizer update on activity callback so that we
                 // we will know about the transition explicitly.
-                final Transition transition = mTransitionController.getCollectingTransition();
-                if (transition == null) {
+                final ActionChain chain = mService.mChainTracker.startDefault("tfTransact");
+                if (chain.mTransition == null) {
                     // This should rarely happen, and we should try to avoid using
                     // {@link #applySyncTransaction} with Shell transition.
                     // We still want to apply and merge the transaction to the active sync
@@ -559,7 +576,7 @@
                                     + " because there is an ongoing sync for"
                                     + " applySyncTransaction().");
                 }
-                applyTransaction(wct, -1 /* syncId */, transition, caller);
+                applyTransaction(wct, -1 /* syncId */, chain, caller);
                 return;
             }
 
@@ -570,8 +587,9 @@
                     transition.abort();
                     return;
                 }
-                if (applyTransaction(wct, -1 /* syncId */, transition, caller, deferred)
-                        == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+                final ActionChain chain = mService.mChainTracker.start("tfTransact", transition);
+                final int effects = applyTransaction(wct, -1 /* syncId */, chain, caller, deferred);
+                if (effects == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
                     transition.abort();
                     return;
                 }
@@ -586,15 +604,10 @@
     }
 
     private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
-            @Nullable Transition transition, @NonNull CallerInfo caller) {
-        return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
-    }
-
-    private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
-            @Nullable Transition transition, @NonNull CallerInfo caller, boolean deferred) {
+            @NonNull ActionChain chain, @NonNull CallerInfo caller, boolean deferred) {
         if (deferred) {
             try {
-                return applyTransaction(t, syncId, transition, caller);
+                return applyTransaction(t, syncId, chain, caller);
             } catch (RuntimeException e) {
                 // If the transaction is deferred, the caller could be from TransitionController
                 // #tryStartCollectFromQueue that executes on system's worker thread rather than
@@ -604,19 +617,17 @@
             }
             return TRANSACT_EFFECTS_NONE;
         }
-        return applyTransaction(t, syncId, transition, caller);
+        return applyTransaction(t, syncId, chain, caller);
     }
 
     /**
      * @param syncId If non-null, this will be a sync-transaction.
-     * @param transition A transition to collect changes into.
+     * @param chain A lifecycle-chain to acculumate changes into.
      * @param caller Info about the calling process.
-     * @param finishTransition The transition that is currently being finished.
      * @return The effects of the window container transaction.
      */
     private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
-            @Nullable Transition transition, @NonNull CallerInfo caller,
-            @Nullable Transition finishTransition) {
+            @NonNull ActionChain chain, @NonNull CallerInfo caller) {
         int effects = TRANSACT_EFFECTS_NONE;
         ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
         mService.deferWindowLayout();
@@ -624,20 +635,21 @@
         boolean deferResume = true;
         mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         boolean deferTransitionReady = false;
-        if (transition != null && !t.isEmpty()) {
-            if (transition.isCollecting()) {
+        if (chain.mTransition != null && !t.isEmpty() && !chain.isFinishing()) {
+            if (chain.mTransition.isCollecting()) {
                 deferTransitionReady = true;
-                transition.deferTransitionReady();
+                chain.mTransition.deferTransitionReady();
             } else {
                 Slog.w(TAG, "Transition is not collecting when applyTransaction."
-                        + " transition=" + transition + " state=" + transition.getState());
-                transition = null;
+                        + " transition=" + chain.mTransition + " state="
+                        + chain.mTransition.getState());
+                chain.mTransition = null;
             }
         }
         try {
             final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
-            if (transition != null) {
-                transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+            if (chain.mTransition != null) {
+                chain.mTransition.applyDisplayChangeIfNeeded(haveConfigChanges);
                 if (!haveConfigChanges.isEmpty()) {
                     effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
                 }
@@ -645,7 +657,7 @@
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
             Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries;
-            if (transition != null) {
+            if (chain.mTransition != null) {
                 // Mark any config-at-end containers before applying config changes so that
                 // the config changes don't dispatch to client.
                 entries = t.getChanges().entrySet().iterator();
@@ -655,7 +667,7 @@
                     if (!entry.getValue().getConfigAtTransitionEnd()) continue;
                     final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
                     if (wc == null || !wc.isAttached()) continue;
-                    transition.setConfigAtEnd(wc);
+                    chain.mTransition.setConfigAtEnd(wc);
                 }
             }
             entries = t.getChanges().entrySet().iterator();
@@ -672,15 +684,13 @@
                 if (syncId >= 0) {
                     addToSyncSet(syncId, wc);
                 }
-                if (transition != null) transition.collect(wc);
+                chain.collect(wc);
 
                 if ((entry.getValue().getChangeMask()
                         & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
                     // Disable entering pip (eg. when recents pretends to finish itself)
-                    if (finishTransition != null) {
-                        finishTransition.setCanPipOnFinish(false /* canPipOnFinish */);
-                    } else if (transition != null) {
-                        transition.setCanPipOnFinish(false /* canPipOnFinish */);
+                    if (chain.mTransition != null) {
+                        chain.mTransition.setCanPipOnFinish(false /* canPipOnFinish */);
                     }
                 }
                 // A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the
@@ -728,9 +738,9 @@
             if (hopSize > 0) {
                 final boolean isInLockTaskMode = mService.isInLockTaskMode();
                 for (int i = 0; i < hopSize; ++i) {
-                    effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,
+                    effects |= applyHierarchyOp(hops.get(i), effects, syncId, chain,
                             isInLockTaskMode, caller, t.getErrorCallbackToken(),
-                            t.getTaskFragmentOrganizer(), finishTransition);
+                            t.getTaskFragmentOrganizer());
                 }
             }
             // Queue-up bounds-change transactions for tasks which are now organized. Do
@@ -789,7 +799,7 @@
             }
         } finally {
             if (deferTransitionReady) {
-                transition.continueTransitionReady();
+                chain.mTransition.continueTransitionReady();
             }
             mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             if (deferResume) {
@@ -1079,9 +1089,9 @@
     }
 
     private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
-            int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
+            int syncId, @NonNull ActionChain chain, boolean isInLockTaskMode,
             @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
-            @Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {
+            @Nullable ITaskFragmentOrganizer organizer) {
         final int type = hop.getType();
         switch (type) {
             case HIERARCHY_OP_TYPE_REMOVE_TASK: {
@@ -1151,7 +1161,7 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
-                effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId,
+                effects |= reparentChildrenTasksHierarchyOp(hop, chain.mTransition, syncId,
                         isInLockTaskMode);
                 break;
             }
@@ -1204,13 +1214,13 @@
                 if (syncId >= 0) {
                     addToSyncSet(syncId, wc);
                 }
-                if (transition != null) {
-                    transition.collect(wc);
+                if (chain.mTransition != null) {
+                    chain.mTransition.collect(wc);
                     if (hop.isReparent()) {
                         if (wc.getParent() != null) {
                             // Collect the current parent. It's visibility may change as
                             // a result of this reparenting.
-                            transition.collect(wc.getParent());
+                            chain.mTransition.collect(wc.getParent());
                         }
                         if (hop.getNewParent() != null) {
                             final WindowContainer parentWc =
@@ -1219,7 +1229,7 @@
                                 Slog.e(TAG, "Can't resolve parent window from token");
                                 break;
                             }
-                            transition.collect(parentWc);
+                            chain.mTransition.collect(parentWc);
                         }
                     }
                 }
@@ -1233,8 +1243,8 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION: {
-                effects |= applyTaskFragmentOperation(hop, transition, isInLockTaskMode, caller,
-                        errorCallbackToken, organizer);
+                effects |= applyTaskFragmentOperation(hop, chain, isInLockTaskMode,
+                        caller, errorCallbackToken, organizer);
                 break;
             }
             case HIERARCHY_OP_TYPE_PENDING_INTENT: {
@@ -1329,7 +1339,7 @@
                 Rect entryBounds = hop.getBounds();
                 mService.mRootWindowContainer.moveActivityToPinnedRootTask(
                         pipActivity, null /* launchIntoPipHostActivity */,
-                        "moveActivityToPinnedRootTask", null /* transition */, entryBounds);
+                        "moveActivityToPinnedRootTask", entryBounds);
 
                 if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) {
                     // Continue the pausing process. This must be done after moving PiP activity to
@@ -1348,13 +1358,13 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
-                if (finishTransition == null) break;
+                if (!chain.isFinishing()) break;
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
                 if (container == null) break;
                 final Task thisTask = container.asActivityRecord() != null
                         ? container.asActivityRecord().getTask() : container.asTask();
                 if (thisTask == null) break;
-                final Task restoreAt = finishTransition.getTransientLaunchRestoreTarget(container);
+                final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container);
                 if (restoreAt == null) break;
                 final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
                 taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
@@ -1444,7 +1454,7 @@
      *         {@link #TRANSACT_EFFECTS_LIFECYCLE} or {@link #TRANSACT_EFFECTS_CLIENT_CONFIG}.
      */
     private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop,
-            @Nullable Transition transition, boolean isInLockTaskMode, @NonNull CallerInfo caller,
+            @NonNull ActionChain chain, boolean isInLockTaskMode, @NonNull CallerInfo caller,
             @Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) {
         if (!validateTaskFragmentOperation(hop, errorCallbackToken, organizer)) {
             return TRANSACT_EFFECTS_NONE;
@@ -1467,7 +1477,7 @@
                     break;
                 }
                 createTaskFragment(taskFragmentCreationParams, errorCallbackToken, caller,
-                        transition);
+                        chain.mTransition);
                 break;
             }
             case OP_TYPE_DELETE_TASK_FRAGMENT: {
@@ -1484,7 +1494,7 @@
                         break;
                     }
                 }
-                effects |= deleteTaskFragment(taskFragment, transition);
+                effects |= deleteTaskFragment(taskFragment, chain.mTransition);
                 break;
             }
             case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
@@ -1533,14 +1543,14 @@
                             opType, exception);
                     break;
                 }
-                if (transition != null) {
-                    transition.collect(activity);
+                if (chain.mTransition != null) {
+                    chain.collect(activity);
                     if (activity.getParent() != null) {
                         // Collect the current parent. Its visibility may change as a result of
                         // this reparenting.
-                        transition.collect(activity.getParent());
+                        chain.collect(activity.getParent());
                     }
-                    transition.collect(taskFragment);
+                    chain.collect(taskFragment);
                 }
                 activity.reparent(taskFragment, POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -1696,8 +1706,8 @@
                 // If any TaskFragment in the Task is collected by the transition, we make the decor
                 // surface visible in sync with the TaskFragment transition. Otherwise, we make the
                 // decor surface visible immediately.
-                final TaskFragment syncTaskFragment = transition != null
-                        ? task.getTaskFragment(transition.mParticipants::contains)
+                final TaskFragment syncTaskFragment = chain.mTransition != null
+                        ? task.getTaskFragment(chain.mTransition.mParticipants::contains)
                         : null;
 
                 if (syncTaskFragment != null) {
@@ -1749,7 +1759,7 @@
                     // The decor surface boost/unboost must be applied after the transition is
                     // completed. Otherwise, the decor surface could be moved before Shell completes
                     // the transition, causing flicker.
-                    runAfterTransition(transition, task::commitDecorSurfaceBoostedState);
+                    runAfterTransition(chain.mTransition, task::commitDecorSurfaceBoostedState);
                 }
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c8e4b0a..7c05c29 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1704,18 +1704,6 @@
         return mActivityRecord != null ? mActivityRecord.getTaskFragment() : null;
     }
 
-    @Nullable Task getRootTask() {
-        final Task task = getTask();
-        if (task != null) {
-            return task.getRootTask();
-        }
-        // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
-        // associate them with some root task to enable dimming.
-        final DisplayContent dc = getDisplayContent();
-        return mAttrs.type >= FIRST_SYSTEM_WINDOW
-                && dc != null ? dc.getDefaultTaskDisplayArea().getRootHomeTask() : null;
-    }
-
     /**
      * Retrieves the visible bounds of the window.
      * @param bounds The rect which gets the bounds.
@@ -2570,10 +2558,9 @@
             return false;
         }
 
-        final Task rootTask = getRootTask();
-        if (rootTask != null && !rootTask.isFocusable()) {
-            // Ignore when the root task shouldn't receive input event.
-            // (i.e. the minimized root task in split screen mode.)
+        final Task task = getTask();
+        if (task != null && !task.isFocusable()) {
+            // The task can be set as non-focusable, e.g. swapping split-screen sides.
             return false;
         }
 
@@ -2599,7 +2586,7 @@
         }
 
         // Don't allow transient-launch activities to take IME.
-        if (rootTask != null && mActivityRecord != null
+        if (task != null && mActivityRecord != null
                 && mTransitionController.isTransientLaunch(mActivityRecord)) {
             return false;
         }
@@ -2785,11 +2772,9 @@
                 // means we need to intercept touches outside of that window. The dim layer
                 // user associated with the window (task or root task) will give us the good
                 // bounds, as they would be used to display the dim layer.
-                final TaskFragment taskFragment = getTaskFragment();
+                final TaskFragment taskFragment = mActivityRecord.getTaskFragment();
                 if (taskFragment != null) {
                     taskFragment.getDimBounds(mTmpRect);
-                } else if (getRootTask() != null) {
-                    getRootTask().getDimBounds(mTmpRect);
                 }
             }
         }
@@ -3934,14 +3919,6 @@
         }
     }
 
-    private int getRootTaskId() {
-        final Task rootTask = getRootTask();
-        if (rootTask == null) {
-            return INVALID_TASK_ID;
-        }
-        return rootTask.mTaskId;
-    }
-
     public void registerFocusObserver(IWindowFocusObserver observer) {
         synchronized (mWmService.mGlobalLock) {
             if (mFocusCallbacks == null) {
@@ -4077,7 +4054,12 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(DISPLAY_ID, getDisplayId());
-        proto.write(STACK_ID, getRootTaskId());
+        int rootTaskId = INVALID_TASK_ID;
+        final Task task = getTask();
+        if (task != null) {
+            rootTaskId = task.getRootTaskId();
+        }
+        proto.write(STACK_ID, rootTaskId);
         mAttrs.dumpDebug(proto, ATTRIBUTES);
         mGivenContentInsets.dumpDebug(proto, GIVEN_CONTENT_INSETS);
         mWindowFrames.dumpDebug(proto, WINDOW_FRAMES);
@@ -4135,8 +4117,9 @@
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.print(prefix + "mDisplayId=" + getDisplayId());
-        if (getRootTask() != null) {
-            pw.print(" rootTaskId=" + getRootTaskId());
+        final Task task = getTask();
+        if (task != null) {
+            pw.print(" taskId=" + task.mTaskId);
         }
         pw.println(" mSession=" + mSession
                 + " mClient=" + mClient.asBinder());
@@ -4666,17 +4649,6 @@
         if (!isImeLayeringTarget()) {
             return false;
         }
-        if (!com.android.window.flags.Flags.doNotSkipImeByTargetVisibility()) {
-            // Note that we don't process IME window if the IME input target is not on the screen.
-            // In case some unexpected IME visibility cases happen like starting the remote
-            // animation on the keyguard but seeing the IME window that originally on the app
-            // which behinds the keyguard.
-            final WindowState imeInputTarget = getImeInputTarget();
-            if (imeInputTarget != null
-                    && !(imeInputTarget.isDrawn() || imeInputTarget.isVisibleRequested())) {
-                return false;
-            }
-        }
         return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
     }
 
diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
index 70c66de..d33313e 100644
--- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java
@@ -43,7 +43,7 @@
     // All desktop mode related flags to be overridden by developer option toggle will be added here
     DESKTOP_WINDOWING_MODE(
             Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
-    DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true);
+    DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 6a0dd5a..5eec012 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1325,27 +1325,7 @@
         pw.print("encryptionRequested=");
         pw.println(encryptionRequested);
 
-        if (!Flags.dumpsysPolicyEngineMigrationEnabled()) {
-            pw.print("disableCamera=");
-            pw.println(disableCamera);
-
-            pw.print("disableScreenCapture=");
-            pw.println(disableScreenCapture);
-
-            pw.print("requireAutoTime=");
-            pw.println(requireAutoTime);
-
-            if (permittedInputMethods != null) {
-                pw.print("permittedInputMethods=");
-                pw.println(permittedInputMethods);
-            }
-
-            pw.println("userRestrictions:");
-            UserRestrictionsUtils.dumpRestrictions(pw, "  ", userRestrictions);
-        }
-
-        if (!Flags.policyEngineMigrationV2Enabled()
-                || !Flags.dumpsysPolicyEngineMigrationEnabled()) {
+        if (!Flags.policyEngineMigrationV2Enabled()) {
             pw.print("mUsbDataSignaling=");
             pw.println(mUsbDataSignalingEnabled);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1290fb7..886ae7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -871,6 +871,16 @@
                 EXEMPT_FROM_POWER_RESTRICTIONS, OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS);
     }
 
+    private static final Set<String> METERED_DATA_RESTRICTION_EXEMPT_ROLES =
+            new ArraySet<>();
+    static {
+        // TODO(b/362545319): reference role name from role manager once it's exposed.
+        final String roleDeviceLockController =
+                "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+        METERED_DATA_RESTRICTION_EXEMPT_ROLES.add(roleDeviceLockController);
+        METERED_DATA_RESTRICTION_EXEMPT_ROLES.add(RoleManager.ROLE_FINANCED_DEVICE_KIOSK);
+    }
+
     /**
      * Admin apps targeting Android S+ may not use
      * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
@@ -1959,6 +1969,10 @@
             return UserManager.isHeadlessSystemUserMode();
         }
 
+        List<String> roleManagerGetRoleHoldersAsUser(String role, UserHandle userHandle) {
+            return getRoleManager().getRoleHoldersAsUser(role, userHandle);
+        }
+
         @SuppressWarnings("AndroidFrameworkPendingIntentMutability")
         PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
                 @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
@@ -2726,22 +2740,14 @@
             return;
         }
 
-        if (Flags.securityLogV2Enabled()) {
-            boolean auditLoggingEnabled = Boolean.TRUE.equals(
-                    mDevicePolicyEngine.getResolvedPolicy(
-                            PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
-            boolean securityLoggingEnabled = Boolean.TRUE.equals(
-                    mDevicePolicyEngine.getResolvedPolicy(
-                            PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
-            setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
-            mInjector.runCryptoSelfTest();
-        } else {
-            synchronized (getLockObject()) {
-                mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
-                mInjector.runCryptoSelfTest();
-                maybePauseDeviceWideLoggingLocked();
-            }
-        }
+        boolean auditLoggingEnabled = Boolean.TRUE.equals(
+                mDevicePolicyEngine.getResolvedPolicy(
+                        PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
+        boolean securityLoggingEnabled = Boolean.TRUE.equals(
+                mDevicePolicyEngine.getResolvedPolicy(
+                        PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
+        setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+        mInjector.runCryptoSelfTest();
     }
 
     /**
@@ -3399,7 +3405,7 @@
 
     @GuardedBy("getLockObject()")
     private void maybeMigrateSecurityLoggingPolicyLocked() {
-        if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+        if (mOwners.isSecurityLoggingMigrated()) {
             return;
         }
 
@@ -11487,10 +11493,8 @@
                 pw.println();
                 mStatLogger.dump(pw);
                 pw.println();
-                if (Flags.dumpsysPolicyEngineMigrationEnabled()) {
-                    mDevicePolicyEngine.dump(pw);
-                    pw.println();
-                }
+                mDevicePolicyEngine.dump(pw);
+                pw.println();
                 pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
                 pw.println("Logout user: " + getLogoutUserIdUnchecked());
                 pw.println();
@@ -12690,14 +12694,12 @@
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
 
-        if (Flags.headlessDeviceOwnerSingleUserEnabled()) {
-            // Block this method if the device is in headless main user mode
-            Preconditions.checkCallAuthorization(
-                    !mInjector.userManagerIsHeadlessSystemUserMode()
-                            || getHeadlessDeviceOwnerModeForDeviceOwner()
-                            != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
-                    "createAndManageUser was called while in headless single user mode");
-        }
+        // Block this method if the device is in headless main user mode
+        Preconditions.checkCallAuthorization(
+                !mInjector.userManagerIsHeadlessSystemUserMode()
+                        || getHeadlessDeviceOwnerModeForDeviceOwner()
+                        != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+                "createAndManageUser was called while in headless single user mode");
 
         // Only allow the system user to use this method
         Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
@@ -13984,11 +13986,9 @@
                     UserManager.DISALLOW_THREAD_NETWORK,
                     new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
         }
-        if (Flags.assistContentUserRestrictionEnabled()) {
-            USER_RESTRICTION_PERMISSIONS.put(
-                    UserManager.DISALLOW_ASSIST_CONTENT,
-                    new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
-        }
+        USER_RESTRICTION_PERMISSIONS.put(
+                UserManager.DISALLOW_ASSIST_CONTENT,
+                new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
@@ -16304,9 +16304,6 @@
 
         @Override
         public void enforceSecurityLoggingPolicy(boolean enabled) {
-            if (!Flags.securityLogV2Enabled()) {
-                return;
-            }
             Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL);
             enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled));
@@ -16314,9 +16311,6 @@
 
         @Override
         public void enforceAuditLoggingPolicy(boolean enabled) {
-            if (!Flags.securityLogV2Enabled()) {
-                return;
-            }
             Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL);
             enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled);
@@ -17329,7 +17323,7 @@
                     return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
                 }
 
-                if (Flags.headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+                if (isHeadlessModeSingleUser) {
                     ensureSetUpUser = mUserManagerInternal.getMainUserId();
                     if (ensureSetUpUser == UserHandle.USER_NULL) {
                         return STATUS_HEADLESS_ONLY_SYSTEM_USER;
@@ -17920,15 +17914,28 @@
         });
     }
 
+    private Set<String> getMeteredDataRestrictionExemptPackages(int userId) {
+        final Set<String> exemptPkgs = new ArraySet<>();
+        for (String role: METERED_DATA_RESTRICTION_EXEMPT_ROLES) {
+            String pkg = getRoleHolderPackageNameOnUser(role, userId);
+            if (pkg != null) {
+                exemptPkgs.add(pkg);
+            }
+        }
+
+        return exemptPkgs;
+    }
+
     private List<String> removeInvalidPkgsForMeteredDataRestriction(
             int userId, List<String> pkgNames) {
+        final Set<String> exemptRolePkgs = getMeteredDataRestrictionExemptPackages(userId);
         synchronized (getLockObject()) {
             final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
             final List<String> excludedPkgs = new ArrayList<>();
             for (int i = pkgNames.size() - 1; i >= 0; --i) {
                 final String pkgName = pkgNames.get(i);
-                // If the package is an active admin, don't restrict it.
-                if (activeAdmins.contains(pkgName)) {
+                // If the package is an active admin or exempt role, don't restrict it.
+                if (activeAdmins.contains(pkgName) || exemptRolePkgs.contains(pkgName)) {
                     excludedPkgs.add(pkgName);
                     continue;
                 }
@@ -18252,45 +18259,20 @@
         }
         final CallerIdentity caller = getCallerIdentity(who, packageName);
 
-        if (Flags.securityLogV2Enabled()) {
-            EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            if (enabled) {
-                mDevicePolicyEngine.setGlobalPolicy(
-                        PolicyDefinition.SECURITY_LOGGING,
-                        admin,
-                        new BooleanPolicyValue(true));
-            } else {
-                mDevicePolicyEngine.removeGlobalPolicy(
-                        PolicyDefinition.SECURITY_LOGGING,
-                        admin);
-            }
+        EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                who,
+                MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
+        if (enabled) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.SECURITY_LOGGING,
+                    admin,
+                    new BooleanPolicyValue(true));
         } else {
-            synchronized (getLockObject()) {
-                if (who != null) {
-                    Preconditions.checkCallAuthorization(
-                            isProfileOwnerOfOrganizationOwnedDevice(caller)
-                                    || isDefaultDeviceOwner(caller));
-                } else {
-                    // A delegate app passes a null admin component, which is expected
-                    Preconditions.checkCallAuthorization(
-                            isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
-                }
-
-                if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
-                    return;
-                }
-                mInjector.securityLogSetLoggingEnabledProperty(enabled);
-                if (enabled) {
-                    mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
-                    maybePauseDeviceWideLoggingLocked();
-                } else {
-                    mSecurityLogMonitor.stop();
-                }
-            }
+            mDevicePolicyEngine.removeGlobalPolicy(
+                    PolicyDefinition.SECURITY_LOGGING,
+                    admin);
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED)
@@ -18312,29 +18294,14 @@
             return mInjector.securityLogGetLoggingEnabledProperty();
         }
 
-        if (Flags.securityLogV2Enabled()) {
-            final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
-                    PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
-            return Boolean.TRUE.equals(policy);
-        } else {
-            synchronized (getLockObject()) {
-                if (admin != null) {
-                    Preconditions.checkCallAuthorization(
-                            isProfileOwnerOfOrganizationOwnedDevice(caller)
-                                    || isDefaultDeviceOwner(caller));
-                } else {
-                    // A delegate app passes a null admin component, which is expected
-                    Preconditions.checkCallAuthorization(
-                            isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
-                }
-                return mInjector.securityLogGetLoggingEnabledProperty();
-            }
-        }
+        final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                admin,
+                MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
+        final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+        return Boolean.TRUE.equals(policy);
     }
 
     private void recordSecurityLogRetrievalTime() {
@@ -18410,42 +18377,24 @@
 
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
 
-        if (Flags.securityLogV2Enabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
-                    caller.getPackageName(),
-                    caller.getUserId());
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                admin,
+                MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
 
-            synchronized (getLockObject()) {
-                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                        || areAllUsersAffiliatedWithDeviceLocked());
-            }
-
-            Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
-                    PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
-
-            if (!Boolean.TRUE.equals(policy)) {
-                Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
-                        caller.getPackageName());
-                return null;
-            }
-        } else {
-            if (admin != null) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller)
-                                || isDefaultDeviceOwner(caller));
-            } else {
-                // A delegate app passes a null admin component, which is expected
-                Preconditions.checkCallAuthorization(
-                        isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
-            }
+        synchronized (getLockObject()) {
             Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
                     || areAllUsersAffiliatedWithDeviceLocked());
+        }
 
-            if (!mInjector.securityLogGetLoggingEnabledProperty()) {
-                return null;
-            }
+        Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.SECURITY_LOGGING, enforcingAdmin);
+
+        if (!Boolean.TRUE.equals(policy)) {
+            Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs",
+                    caller.getPackageName());
+            return null;
         }
 
         recordSecurityLogRetrievalTime();
@@ -18465,10 +18414,6 @@
         }
         final CallerIdentity caller = getCallerIdentity(callingPackage);
 
-        if (!Flags.securityLogV2Enabled()) {
-            throw new UnsupportedOperationException("Audit log not enabled");
-        }
-
         EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                 null /* admin */,
                 MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
@@ -18493,10 +18438,6 @@
             return false;
         }
 
-        if (!Flags.securityLogV2Enabled()) {
-            throw new UnsupportedOperationException("Audit log not enabled");
-        }
-
         final CallerIdentity caller = getCallerIdentity(callingPackage);
         EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                 null /* admin */,
@@ -19839,16 +19780,14 @@
     }
 
     private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
-        if (Flags.esimManagementEnabled()) {
-            SubscriptionManager subscriptionManager = mContext.getSystemService(
-                    SubscriptionManager.class);
-            for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
-                try {
-                    subscriptionManager.setGroupOwner(subId, target.getPackageName());
-                } catch (Exception e) {
-                    // Shouldn't happen.
-                    Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
-                }
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+            try {
+                subscriptionManager.setGroupOwner(subId, target.getPackageName());
+            } catch (Exception e) {
+                // Shouldn't happen.
+                Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
             }
         }
     }
@@ -20746,9 +20685,7 @@
                     // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
                     // code below grants that app op, and once the exemption is in place, the user
                     // won't be able to disable background usage anymore.
-                    if (Flags.powerExemptionBgUsageFix()
-                            && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
-                            && newMode == MODE_ALLOWED) {
+                    if (exemption == EXEMPT_FROM_POWER_RESTRICTIONS && newMode == MODE_ALLOWED) {
                         setBgUsageAppOp(appOpsMgr, appInfo);
                     }
                 }
@@ -21839,8 +21776,6 @@
      */
     @Nullable
     private String getRoleHolderPackageNameOnUser(String role, int userId) {
-        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
-
         // Clear calling identity as the RoleManager APIs require privileged permissions.
         return mInjector.binderWithCleanCallingIdentity(() -> {
             List<UserInfo> users;
@@ -21852,7 +21787,7 @@
             }
             for (UserInfo user : users) {
                 List<String> roleHolders =
-                        roleManager.getRoleHoldersAsUser(role, user.getUserHandle());
+                        mInjector.roleManagerGetRoleHoldersAsUser(role, user.getUserHandle());
                 if (!roleHolders.isEmpty()) {
                     return roleHolders.get(0);
                 }
@@ -22145,8 +22080,8 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
-            int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
-                    && isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
+            int deviceOwnerUserId =
+                    isSingleUserMode && mInjector.userManagerIsHeadlessSystemUserMode()
                     ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
 
             if (!removeNonRequiredAppsForManagedDevice(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 2ea5f16..52a7845 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -410,9 +410,8 @@
             out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
             out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
             out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
-            if (Flags.securityLogV2Enabled()) {
-                out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
-            }
+            out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
+
             if (Flags.unmanagedModeMigration()) {
                 out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
                         mRequiredPasswordComplexityMigrated);
@@ -483,8 +482,8 @@
                             null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
                     mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
                             null, ATTR_MIGRATED_POST_UPGRADE, false);
-                    mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
-                            && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+                    mSecurityLoggingMigrated =
+                            parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
                     mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
                             && parser.getAttributeBoolean(null,
                                     ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 19a942c..24ee46f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -536,7 +536,6 @@
             USER_RESTRICTION_FLAGS.put(
                     UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
         }
-        USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
         for (String key : USER_RESTRICTION_FLAGS.keySet()) {
             createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index e1cb37d..8068d46 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -238,9 +238,7 @@
             }
 
             for (int user : resolveUsers(userId)) {
-                if (Flags.disallowUserControlBgUsageFix()) {
-                    setBgUsageAppOp(packages, pmi, user, appOpsManager);
-                }
+                setBgUsageAppOp(packages, pmi, user, appOpsManager);
                 if (Flags.disallowUserControlStoppedStateFix()) {
                     for (String packageName : packages) {
                         pmi.setPackageStoppedState(packageName, false, user);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index dd049303..474c48a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -23,7 +23,6 @@
 import android.app.admin.IAuditLogEventsCallback;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.flags.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
@@ -184,28 +183,6 @@
     @GuardedBy("mLock")
     private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>();
 
-    /**
-     * Start security logging.
-     *
-     * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
-     *     users on the device.
-     */
-    void start(int enabledUser) {
-        Slog.i(TAG, "Starting security logging for user " + enabledUser);
-        mEnabledUser = enabledUser;
-        mLock.lock();
-        try {
-            if (mMonitorThread == null) {
-                resetLegacyBufferLocked();
-                startMonitorThreadLocked();
-            } else {
-                Slog.i(TAG, "Security log monitor thread is already running");
-            }
-        } finally {
-            mLock.unlock();
-        }
-    }
-
     void stop() {
         Slog.i(TAG, "Stopping security logging.");
         mLock.lock();
@@ -467,11 +444,11 @@
             assignLogId(event);
         }
 
-        if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+        if (mLegacyLogEnabled) {
             addToLegacyBufferLocked(dedupedLogs);
         }
 
-        if (Flags.securityLogV2Enabled() && mAuditLogEnabled) {
+        if (mAuditLogEnabled) {
             addAuditLogEventsLocked(dedupedLogs);
         }
     }
@@ -548,7 +525,7 @@
                 saveLastEvents(newLogs);
                 newLogs.clear();
 
-                if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
+                if (mLegacyLogEnabled) {
                     notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
                 }
             } catch (IOException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 09c54cb..c5c371f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,7 +106,9 @@
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.policy.AttributeCache;
-import com.android.internal.protolog.ProtoLogService;
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogConfigurationService;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
@@ -256,6 +258,7 @@
 import com.android.server.stats.pull.StatsPullAtomService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.supervision.SupervisionService;
 import com.android.server.systemcaptions.SystemCaptionsManagerService;
 import com.android.server.telecom.TelecomLoaderService;
 import com.android.server.testharness.TestHarnessModeService;
@@ -1092,11 +1095,16 @@
 
         // Orchestrates some ProtoLogging functionality.
         if (android.tracing.Flags.clientSideProtoLogging()) {
-            t.traceBegin("StartProtoLogService");
-            ServiceManager.addService(Context.PROTOLOG_SERVICE, new ProtoLogService());
+            t.traceBegin("StartProtoLogConfigurationService");
+            ServiceManager.addService(
+                    Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService());
             t.traceEnd();
         }
 
+        t.traceBegin("InitializeProtoLog");
+        ProtoLog.init(ProtoLogGroup.values());
+        t.traceEnd();
+
         // Platform compat service is used by ActivityManagerService, PackageManagerService, and
         // possibly others in the future. b/135010838.
         t.traceBegin("PlatformCompat");
@@ -1597,6 +1605,12 @@
             mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
             t.traceEnd();
 
+            if (android.app.supervision.flags.Flags.supervisionApi()) {
+                t.traceBegin("StartSupervisionService");
+                mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
+                t.traceEnd();
+            }
+
             if (!isTv) {
                 t.traceBegin("StartVibratorManagerService");
                 mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
diff --git a/services/supervision/Android.bp b/services/supervision/Android.bp
new file mode 100644
index 0000000..93a0c4a
--- /dev/null
+++ b/services/supervision/Android.bp
@@ -0,0 +1,22 @@
+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"],
+}
+
+filegroup {
+    name: "services.supervision-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.supervision",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.supervision-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
new file mode 100644
index 0000000..a4ef629
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.supervision;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.supervision.ISupervisionManager;
+import android.content.Context;
+
+
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/** Service for handling system supervision. */
+public class SupervisionService extends ISupervisionManager.Stub {
+    private static final String LOG_TAG = "SupervisionService";
+
+    private final Context mContext;
+
+    public SupervisionService(Context context) {
+        mContext = context.createAttributionContext("SupervisionService");
+    }
+
+    @Override
+    public boolean isSupervisionEnabled() {
+        return false;
+    }
+
+    @Override
+    protected void dump(@NonNull FileDescriptor fd,
+            @NonNull PrintWriter fout, @Nullable String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
+
+        fout.println("Supervision enabled: " + isSupervisionEnabled());
+    }
+
+    public static class Lifecycle extends SystemService {
+        private final SupervisionService mSupervisionService;
+
+        public Lifecycle(@NonNull Context context) {
+            super(context);
+            mSupervisionService = new SupervisionService(context);
+        }
+
+        @Override
+        public void onStart() {
+            publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index ab0f0c1..d91f154 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3556,12 +3556,16 @@
                 new RefreshRateRange(refreshRate, refreshRate);
         displayListener.onDisplayChanged(DISPLAY_ID);
 
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
         assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, refreshRate, refreshRate);
 
         mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
         displayListener.onDisplayChanged(DISPLAY_ID);
 
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+        assertNull(vote);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
         assertNull(vote);
     }
@@ -3585,6 +3589,8 @@
 
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
         assertNull(vote);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_REFRESH_RATE);
+        assertNull(vote);
     }
 
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 54f4607..1abc557 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -18,7 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -27,7 +29,9 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.service.dreams.DreamOverlayService;
+import android.service.dreams.Flags;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.service.dreams.IDreamOverlayClient;
@@ -136,7 +140,7 @@
 
         // Start the dream.
         client.startDream(mLayoutParams, mOverlayCallback,
-                FIRST_DREAM_COMPONENT.flattenToString(), false);
+                FIRST_DREAM_COMPONENT.flattenToString(), false, false);
 
         // The callback should not have run yet.
         verify(monitor, never()).onStartDream();
@@ -194,22 +198,24 @@
         // Start a dream with the first client and ensure the dream is now active from the
         // overlay's perspective.
         firstClient.startDream(mLayoutParams, mOverlayCallback,
-                FIRST_DREAM_COMPONENT.flattenToString(), false);
+                FIRST_DREAM_COMPONENT.flattenToString(), true, false);
 
 
         verify(monitor).onStartDream();
         assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
+        assertThat(service.isDreamInPreviewMode()).isTrue();
 
         Mockito.clearInvocations(monitor);
 
         // Start a dream from the second client and verify that the overlay has both cycled to
         // the new dream (ended/started).
         secondClient.startDream(mLayoutParams, mOverlayCallback,
-                SECOND_DREAM_COMPONENT.flattenToString(), false);
+                SECOND_DREAM_COMPONENT.flattenToString(), false, false);
 
         verify(monitor).onEndDream();
         verify(monitor).onStartDream();
         assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
+        assertThat(service.isDreamInPreviewMode()).isFalse();
 
         Mockito.clearInvocations(monitor);
 
@@ -221,6 +227,47 @@
         verify(monitor, never()).onWakeUp();
     }
 
+    /**
+     * Verifies that only the currently started dream is able to affect the overlay.
+     */
+    @Test
+    @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT)
+    public void testRedirectToWakeAcrossClients() throws RemoteException {
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mExecutor).execute(any());
+
+        final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+                TestDreamOverlayService.Monitor.class);
+        final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
+        final IBinder binder = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+        service.redirectWake(true);
+
+        final IDreamOverlayClient client = getClient(overlay);
+
+        // Start the dream.
+        client.startDream(mLayoutParams, mOverlayCallback,
+                FIRST_DREAM_COMPONENT.flattenToString(), false, false);
+        // Make sure redirect state is set on dream.
+        verify(mOverlayCallback).onRedirectWake(eq(true));
+
+        // Make sure new changes are propagated.
+        clearInvocations(mOverlayCallback);
+        service.redirectWake(false);
+        verify(mOverlayCallback).onRedirectWake(eq(false));
+
+
+        // Start another dream, make sure new dream is informed of current state.
+        service.redirectWake(true);
+        clearInvocations(mOverlayCallback);
+        client.startDream(mLayoutParams, mOverlayCallback,
+                FIRST_DREAM_COMPONENT.flattenToString(), false, false);
+        verify(mOverlayCallback).onRedirectWake(eq(true));
+    }
+
     private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
         final OverlayClientCallback callback = new OverlayClientCallback();
         overlay.getClient(callback);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index 43aa7fe..7c239ef 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -385,7 +385,8 @@
             final ArgumentCaptor<IDreamOverlayCallback> overlayCallbackCaptor =
                     ArgumentCaptor.forClass(IDreamOverlayCallback.class);
             verify(mDreamOverlayClient, description("dream client not informed of dream start"))
-                    .startDream(any(), overlayCallbackCaptor.capture(), any(), anyBoolean());
+                    .startDream(any(), overlayCallbackCaptor.capture(), any(), anyBoolean(),
+                            anyBoolean());
 
             mDreamOverlayCallback = overlayCallbackCaptor.getValue();
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 8656b99..51aa528 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -46,6 +46,7 @@
 
 import static com.android.server.am.ActivityManagerService.FOLLOW_UP_OOMADJUSTER_UPDATE_MSG;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
 import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
@@ -844,6 +845,49 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoAll_PreviousApp() {
+        final int numberOfApps = 15;
+        final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
+        for (int i = 0; i < numberOfApps; i++) {
+            apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
+                    MOCKAPP_PROCESSNAME + i, MOCKAPP_PACKAGENAME + i, true));
+            final WindowProcessController wpc = apps[i].getWindowProcessController();
+            doReturn(true).when(wpc).isPreviousProcess();
+            doReturn(true).when(wpc).hasActivities();
+        }
+        mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        setProcessesToLru(apps);
+        mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+
+        for (int i = 0; i < numberOfApps; i++) {
+            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+                    SCHED_GROUP_BACKGROUND, "previous");
+        }
+
+        if (!Flags.followUpOomadjUpdates()) return;
+
+        for (int i = 0; i < numberOfApps; i++) {
+            final ArgumentCaptor<Long> followUpTimeCaptor = ArgumentCaptor.forClass(Long.class);
+            verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
+                    followUpTimeCaptor.capture());
+            mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
+        }
+
+        mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+
+        for (int i = 0; i < numberOfApps; i++) {
+            final int mruIndex = numberOfApps - i - 1;
+            int expectedAdj = CACHED_APP_MIN_ADJ + (mruIndex * 2 * CACHED_APP_IMPORTANCE_LEVELS);
+            if (expectedAdj > CACHED_APP_MAX_ADJ) {
+                expectedAdj = CACHED_APP_MAX_ADJ;
+            }
+            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
+                    SCHED_GROUP_BACKGROUND, "previous-expired");
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_Backup() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 0703db2..18811de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -223,6 +223,11 @@
             mShutDownActionReceiver = receiver;
             return null;
         }
+
+        @Override
+        public int getUserId() {
+            return 0;
+        }
     }
 
     @Before
@@ -237,7 +242,7 @@
         mPackageCategories = new HashMap<>();
         mPackageUids = new HashMap<>();
         mPackageName = mMockContext.getPackageName();
-        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME, -1);
         LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
 
         mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
@@ -245,7 +250,12 @@
     }
 
     private void mockAppCategory(String packageName, int packageUid,
-            @ApplicationInfo.Category int category)
+            @ApplicationInfo.Category int category) throws Exception {
+        mockAppCategory(packageName, packageUid, category, -1 /*userId*/);
+    }
+
+    private void mockAppCategory(String packageName, int packageUid,
+            @ApplicationInfo.Category int category, int userId)
             throws Exception {
         reset(mMockPackageManager);
         mPackageCategories.put(packageName, category);
@@ -259,8 +269,15 @@
             ApplicationInfo applicationInfo = new ApplicationInfo();
             applicationInfo.packageName = packageName;
             applicationInfo.category = category;
-            when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
-                    .thenReturn(applicationInfo);
+            if (userId == -1) {
+                when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+                        anyInt()))
+                        .thenReturn(applicationInfo);
+            } else {
+                when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(),
+                        eq(userId)))
+                        .thenReturn(applicationInfo);
+            }
 
             final PackageInfo pi = new PackageInfo();
             pi.packageName = packageName;
@@ -2331,10 +2348,12 @@
 
     @Test
     public void testGamePowerMode_twoGames() throws Exception {
-        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
         String someGamePkg = "some.game";
         int somePackageId = DEFAULT_PACKAGE_UID + 1;
-        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+                ActivityManager.getCurrentUser());
         HashMap<Integer, Boolean> powerState = new HashMap<>();
         doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
                 .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2354,10 +2373,12 @@
 
     @Test
     public void testGamePowerMode_twoGamesOverlap() throws Exception {
-        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
         String someGamePkg = "some.game";
         int somePackageId = DEFAULT_PACKAGE_UID + 1;
-        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME,
+                ActivityManager.getCurrentUser());
         gameManagerService.mUidObserver.onUidStateChanged(
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
@@ -2372,7 +2393,8 @@
 
     @Test
     public void testGamePowerMode_noPackage() throws Exception {
-        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
         String[] packages = {};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
         gameManagerService.mUidObserver.onUidStateChanged(
@@ -2383,23 +2405,24 @@
     @Test
     public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
-        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
-
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        int userId = ActivityManager.getCurrentUser();
         String nonGamePkg1 = "not.game1";
         int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
-        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
 
         String nonGamePkg2 = "not.game2";
         int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
-        mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+        mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE, userId);
 
         String gamePkg1 = "game1";
         int gameUid1 = DEFAULT_PACKAGE_UID + 3;
-        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
 
         String gamePkg2 = "game2";
         int gameUid2 = DEFAULT_PACKAGE_UID + 4;
-        mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME, userId);
 
         // non-game1 top and background with no-op
         gameManagerService.mUidObserver.onUidStateChanged(
@@ -2470,15 +2493,17 @@
     @Test
     public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
         mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
-        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        int userId = ActivityManager.getCurrentUser();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
 
         String nonGamePkg1 = "not.game1";
         int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
-        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+        mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE, userId);
 
         String gamePkg1 = "game1";
         int gameUid1 = DEFAULT_PACKAGE_UID + 3;
-        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+        mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME, userId);
 
         // non-game1 top and background with no-op
         gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
index 127ab8a..f22279a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
@@ -74,6 +74,7 @@
     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
     private static final int VOLTAGE_MV = 3500;
     private static final int ENERGY_CONSUMER_ID = 777;
+    private static final long START_TIME = 10_000_000_000L;
 
     private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
     @Mock
@@ -113,11 +114,13 @@
             };
 
     private MonotonicClock mMonotonicClock;
+    private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+        mMonotonicClock = new MonotonicClock(START_TIME, mStatsRule.getMockClock());
+        mHistoryItem.clear();
     }
 
     @Test
@@ -129,7 +132,6 @@
 
         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
                 () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
-        stats.start(0);
 
         GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
         collector.addConsumer(
@@ -142,9 +144,11 @@
         stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
 
         // Turn the screen off after 2.5 seconds
-        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
-        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
-        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+                START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+                START_TIME + 5000);
 
         stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
 
@@ -158,7 +162,87 @@
         mStatsRule.setTime(11_000, 11_000);
         collector.collectAndDeliverStats();
 
-        stats.finish(11_000);
+        stats.finish(START_TIME + 11_000);
+
+        PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+        statsLayout.fromExtras(descriptor.extras);
+
+        // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
+        // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
+        // scr-off GNSS=poor: 3000 * 1000 = 0.83333 mAh
+        // scr-off GNSS-on: 0.12500 + 0.83333 = 0.95833 mAh
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.06944);
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.12500 + 0.83333);
+
+        // UID1 =
+        //   scr-on FG: 2500 -> 0.06944 mAh
+        //   scr-off BG: 2500/7500 * 0.95833 = 0.31944 mAh
+        //   scr-off FGS: 1000/7500 * 0.95833 = 0.12777 mAh
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.06944);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.31944);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.12777);
+
+        // UID2 =
+        //   scr-off cached: 4000/7500 * 0.95833 = 0.51111 mAh
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.51111);
+
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0);
+    }
+
+    @Test
+    public void initialStateGnssOn() {
+        // ODPM unsupported
+        when(mConsumedEnergyRetriever
+                .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any()))
+                .thenReturn(new int[0]);
+
+        PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
+                () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
+
+        stats.noteStateChange(buildHistoryItemInitialStateGpsOn(0));
+
+        // Turn the screen off after 2.5 seconds
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+                START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+                START_TIME + 5000);
+
+        stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
+
+        stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
+        stats.noteStateChange(buildHistoryItem(7000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+        stats.noteStateChange(buildHistoryItem(8000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+        mStatsRule.setTime(11_000, 11_000);
+
+        stats.finish(START_TIME + 11_000);
 
         PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -224,8 +308,6 @@
                 powerStats -> stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime()));
         collector.setEnabled(true);
 
-        stats.start(0);
-
         // Establish a baseline
         when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
                 .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 10000));
@@ -234,9 +316,11 @@
         stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
 
         // Turn the screen off after 2.5 seconds
-        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
-        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
-        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND,
+                START_TIME + 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+                START_TIME + 5000);
 
         stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
 
@@ -245,16 +329,14 @@
         collector.collectAndDeliverStats();
 
         stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
-        stats.noteStateChange(buildHistoryItem(7000,
-                GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
-        stats.noteStateChange(buildHistoryItem(8000,
-                GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+        stats.noteStateChange(buildHistoryItem(7000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+        stats.noteStateChange(buildHistoryItem(8000, GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
         mStatsRule.setTime(11_000, 11_000);
         when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{ENERGY_CONSUMER_ID}))
                 .thenReturn(createEnergyConsumerResults(ENERGY_CONSUMER_ID, 3_610_000));
         collector.collectAndDeliverStats();
 
-        stats.finish(11_000);
+        stats.finish(START_TIME + 11_000);
 
         PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -313,33 +395,45 @@
                 .isWithin(PRECISION).of(0);
     }
 
-    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
-            int uid) {
+    private BatteryStats.HistoryItem buildHistoryItemInitialStateGpsOn(long timestamp) {
         mStatsRule.setTime(timestamp, timestamp);
-        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
-        historyItem.time = mMonotonicClock.monotonicTime();
-        historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
-        if (stateOn) {
-            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
-                    | BatteryStats.HistoryItem.EVENT_FLAG_START;
-        } else {
-            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
-                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
-        }
-        historyItem.eventTag = historyItem.localEventTag;
-        historyItem.eventTag.uid = uid;
-        historyItem.eventTag.string = "gnss";
-        return historyItem;
+        mHistoryItem.time = mMonotonicClock.monotonicTime();
+        mHistoryItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
+        setGnssSignalLevel(BatteryStats.HistoryItem.GNSS_SIGNAL_QUALITY_NONE);
+        return mHistoryItem;
     }
 
-    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, int signalLevel) {
+    private BatteryStats.HistoryItem buildHistoryItem(long timestamp, boolean stateOn,
+            int uid) {
         mStatsRule.setTime(timestamp, timestamp);
-        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
-        historyItem.time = mMonotonicClock.monotonicTime();
-        historyItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
-        historyItem.states2 =
-                signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
-        return historyItem;
+        mHistoryItem.time = mMonotonicClock.monotonicTime();
+        mHistoryItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
+        if (stateOn) {
+            mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_START;
+        } else {
+            mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+        }
+        mHistoryItem.eventTag = mHistoryItem.localEventTag;
+        mHistoryItem.eventTag.uid = uid;
+        mHistoryItem.eventTag.string = "gnss";
+        return mHistoryItem;
+    }
+
+    private BatteryStats.HistoryItem buildHistoryItem(long timestamp, int signalLevel) {
+        mStatsRule.setTime(timestamp, timestamp);
+        mHistoryItem.time = mMonotonicClock.monotonicTime();
+        setGnssSignalLevel(signalLevel);
+        mHistoryItem.eventCode = BatteryStats.HistoryItem.EVENT_NONE;
+        mHistoryItem.eventTag = null;
+        return mHistoryItem;
+    }
+
+    private void setGnssSignalLevel(int signalLevel) {
+        mHistoryItem.states2 =
+                (mHistoryItem.states2 & ~BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+                        | signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
     }
 
     private int[] states(int... states) {
@@ -362,12 +456,14 @@
         AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
         PowerComponentAggregatedPowerStats powerComponentStats =
                 aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
-        powerComponentStats.start(0);
+        powerComponentStats.start(START_TIME);
 
-        powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
-        powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
-        powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
-        powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+        powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, START_TIME);
+        powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, START_TIME);
+        powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND,
+                START_TIME);
+        powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED,
+                START_TIME);
 
         return powerComponentStats;
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index bc2fd73..2f7b8d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -200,7 +200,8 @@
                     eq(TEST_REQUEST_ID),
                     eq(sensor.getCookie()),
                     anyBoolean() /* allowBackgroundAuthentication */,
-                    anyBoolean() /* isForLegacyFingerprintManager */);
+                    anyBoolean() /* isForLegacyFingerprintManager */,
+                    eq(false) /* isMandatoryBiometrics */);
         }
 
         final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index d2961bc..6b8e414 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -636,7 +636,8 @@
                 eq(TEST_REQUEST_ID),
                 cookieCaptor.capture() /* cookie */,
                 anyBoolean() /* allowBackgroundAuthentication */,
-                anyBoolean() /* isForLegacyFingerprintManager */);
+                anyBoolean() /* isForLegacyFingerprintManager */,
+                eq(false) /* isMandatoryBiometrics */);
 
         // onReadyForAuthentication, mAuthSession state OK
         mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 4604b31..613cb20 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -98,7 +98,8 @@
                 @NonNull ClientMonitorCallbackConverter callback) {
             super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
                     TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+                    mock(BiometricLogger.class), mock(BiometricContext.class),
+                    false /* isMandatoryBiometrics */);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index ffc7811..4f07380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -62,7 +62,8 @@
             extends HalClientMonitor<T> {
         public InterruptableMonitor() {
             super(null, null, null, null, 0, null, 0, 0,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+                    mock(BiometricLogger.class), mock(BiometricContext.class),
+                    false /* isMandatoryBiometrics */);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 36a7b3d..90c07d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1051,6 +1051,11 @@
         public String getAttributionTag() {
             return null;
         }
+
+        @Override
+        public boolean isMandatoryBiometrics() {
+            return false;
+        }
     }
 
     private static class TestAuthenticationClient
@@ -1176,7 +1181,7 @@
                 @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG,
                     cookie, TEST_SENSOR_ID, mock(BiometricLogger.class),
-                    mock(BiometricContext.class));
+                    mock(BiometricContext.class), false /* isMandatoryBiometrics */);
             mProtoEnum = protoEnum;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index f477682..6ac95c8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -109,6 +109,8 @@
     private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
+    private BiometricUtils<Face> mBiometricUtils;
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
@@ -213,7 +215,7 @@
                 mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
                 true /* debugConsent */,
                 (new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build(),
-                mAuthenticationStateListeners);
+                mAuthenticationStateListeners, mBiometricUtils);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 6780e60..bf97086 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -50,6 +50,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -90,6 +91,8 @@
     private AidlSession mCurrentSession;
     @Mock
     private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    private FaceUtils mBiometricUtils;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -114,7 +117,7 @@
                 mUserSwitchProvider);
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mAidlResponseHandlerCallback);
+                mAidlResponseHandlerCallback, mBiometricUtils);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 4248e5e..24ce569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -206,7 +206,7 @@
                 new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
                 SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
                 false /* debugConsent */, (new FaceEnrollOptions.Builder()).build(),
-                mAuthenticationStateListeners));
+                mAuthenticationStateListeners, mBiometricUtils));
         mLooper.dispatchAll();
 
         verify(mAidlResponseHandlerCallback).onEnrollSuccess();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 698db2e..4ef8782 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -51,6 +51,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserSwitchProvider;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
@@ -96,6 +97,8 @@
     private HandlerThread mThread;
     @Mock
     AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    @Mock
+    private FingerprintUtils mBiometricUtils;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
@@ -121,7 +124,7 @@
                 mUserSwitchProvider);
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
-                mAidlResponseHandlerCallback);
+                mAidlResponseHandlerCallback, mBiometricUtils);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b4cc343..698bda3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -60,6 +60,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -325,6 +326,11 @@
         }
 
         @Override
+        List<String> roleManagerGetRoleHoldersAsUser(String role, UserHandle userHandle) {
+            return services.roleManagerForMock.getRoleHoldersAsUser(role, userHandle);
+        }
+
+        @Override
         PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
                 Intent intent, int flags, Bundle options, UserHandle user) {
             return null;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b7483d6..cb4269a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -109,6 +109,7 @@
 import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.WifiSsidPolicy;
+import android.app.role.RoleManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -2889,6 +2890,52 @@
     }
 
     @Test
+    public void testSetMeteredDataDisabledPackagesExemptRoles() throws Exception {
+        // TODO(b/362545319): reference role name from role manager once it's exposed.
+        final String controllerRole = "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+
+        setAsProfileOwner(admin1);
+
+        assertThat(dpm.getMeteredDataDisabledPackages(admin1)).isEmpty();
+
+        // Setup
+        final ArrayList<String> pkgsToRestrict = new ArrayList<>();
+        final ArrayList<String> pkgsExpectedAsNotRestricted = new ArrayList<>();
+        final String packageWithControllerRole = "com.example.controller";
+        final String packageWithKioskRole = "com.example.kiosk";
+        final String packageWithNotExemptRole = "com.example.notexempt";
+
+        pkgsToRestrict.add(packageWithControllerRole);
+        pkgsToRestrict.add(packageWithKioskRole);
+        pkgsToRestrict.add(packageWithNotExemptRole);
+
+        pkgsExpectedAsNotRestricted.add(packageWithControllerRole);
+        pkgsExpectedAsNotRestricted.add(packageWithKioskRole);
+
+        setupPackageInPackageManager(packageWithControllerRole, CALLER_USER_HANDLE, 123, 0);
+        setupPackageInPackageManager(packageWithKioskRole, CALLER_USER_HANDLE, 456, 0);
+        setupPackageInPackageManager(packageWithNotExemptRole, CALLER_USER_HANDLE, 789, 0);
+
+        when(getServices().roleManagerForMock.getRoleHoldersAsUser(controllerRole,
+                UserHandle.of(CALLER_USER_HANDLE)))
+                .thenReturn(new ArrayList<>(Arrays.asList(packageWithControllerRole)));
+        when(getServices().roleManagerForMock.getRoleHoldersAsUser(
+                RoleManager.ROLE_FINANCED_DEVICE_KIOSK,
+                UserHandle.of(CALLER_USER_HANDLE)))
+                .thenReturn(new ArrayList<>(Arrays.asList(packageWithKioskRole)));
+
+        List<String> excludedPkgs = dpm.setMeteredDataDisabledPackages(admin1, pkgsToRestrict);
+
+        // Verify
+        assertThat(excludedPkgs).containsExactlyElementsIn(pkgsExpectedAsNotRestricted);
+        assertThat(dpm.getMeteredDataDisabledPackages(admin1))
+                .isEqualTo(Arrays.asList(packageWithNotExemptRole));
+        verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+                MockUtils.checkApps(packageWithNotExemptRole),
+                eq(CALLER_USER_HANDLE));
+    }
+
+    @Test
     public void testSetGetMeteredDataDisabledPackages_deviceAdmin() {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         dpm.setActiveAdmin(admin1, true);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 76aa40c..2e200a9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -142,6 +142,7 @@
     public final DevicePolicyManager devicePolicyManager;
     public final LocationManager locationManager;
     public final RoleManager roleManager;
+    public final RoleManagerForMock roleManagerForMock;
     public final SubscriptionManager subscriptionManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -200,6 +201,7 @@
         devicePolicyManager = mock(DevicePolicyManager.class);
         locationManager = mock(LocationManager.class);
         roleManager = realContext.getSystemService(RoleManager.class);
+        roleManagerForMock = mock(RoleManagerForMock.class);
         subscriptionManager = mock(SubscriptionManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
@@ -495,6 +497,12 @@
         }
     }
 
+    public static class RoleManagerForMock {
+        public List<String> getRoleHoldersAsUser(String role, UserHandle userHandle) {
+            return new ArrayList<>();
+        }
+    }
+
     public static class SettingsForMock {
         public int settingsSecureGetIntForUser(String name, int def, int userHandle) {
             return 0;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 17b499e..d6f7e21 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -625,25 +625,10 @@
 
         // pretend reboot happens here
         when(mInjected.getBootCount()).thenReturn(1);
-        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
-        doNothing()
-                .when(mInjected)
-                .reportMetric(
-                        metricsSuccessCaptor.capture(),
-                        metricsErrorCodeCaptor.capture(),
-                        eq(2) /* Server based */,
-                        eq(1) /* attempt count */,
-                        anyInt(),
-                        eq(0) /* vbmeta status */,
-                        anyInt());
+
         mService.loadRebootEscrowDataIfAvailable(null);
         verify(mServiceConnection, never()).unwrap(any(), anyLong());
         verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
-        assertFalse(metricsSuccessCaptor.getValue());
-        assertEquals(
-                Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA),
-                metricsErrorCodeCaptor.getValue());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 55c48e0..f0a5f75 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -36,7 +36,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,11 +54,6 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
-    @Before
-    public void setUp() {
-        mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
-    }
-
     @Test
     public void testNonNull() {
         Bundle out = UserRestrictionsUtils.nonNull(null);
@@ -144,7 +138,6 @@
 
     @Test
     public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_orgOwned() {
-        mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_SIM_GLOBALLY,
                 false,
@@ -157,7 +150,6 @@
 
     @Test
     public void testCanProfileOwnerChange_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
-        mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_SIM_GLOBALLY,
                 false,
@@ -169,22 +161,7 @@
     }
 
     @Test
-    public void
-            testCanProfileOwnerChange_disabled_restrictionRequiresOrgOwnedDevice_notOrgOwned() {
-        mSetFlagsRule.disableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
-        assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
-                UserManager.DISALLOW_SIM_GLOBALLY,
-                false,
-                false));
-        assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
-                UserManager.DISALLOW_SIM_GLOBALLY,
-                true,
-                false));
-    }
-
-    @Test
     public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_orgOwned() {
-        mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_ADJUST_VOLUME,
                 false,
@@ -197,7 +174,6 @@
 
     @Test
     public void testCanProfileOwnerChange_restrictionNotRequiresOrgOwnedDevice_notOrgOwned() {
-        mSetFlagsRule.enableFlags(android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
                 UserManager.DISALLOW_ADJUST_VOLUME,
                 false,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 3fc28f8..b97a268 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
 import static android.app.Notification.COLOR_DEFAULT;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
@@ -36,6 +37,7 @@
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS;
 import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
 import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
 import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -2575,7 +2577,11 @@
         assertThat(cachedSummary).isNull();
     }
 
-    private void checkNonGroupableNotifications() {
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+    public void testNonGroupableNotifications() {
+        // Check that there is no valid section for: conversations, calls, foreground services
         NotificationRecord notification_conversation = mock(NotificationRecord.class);
         when(notification_conversation.isConversation()).thenReturn(true);
         assertThat(GroupHelper.getSection(notification_conversation)).isNull();
@@ -2648,8 +2654,6 @@
                 "", false, recsChannel);
         assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
                 "AlertingSection");
-
-        checkNonGroupableNotifications();
     }
 
     @Test
@@ -2694,8 +2698,86 @@
                 "", false, recsChannel);
         assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
                 "RecsSection");
+    }
 
-        checkNonGroupableNotifications();
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+    public void testNonGroupableNotifications_forceGroupConversations() {
+        // Check that there is no valid section for: calls, foreground services
+        NotificationRecord notification_call = spy(getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_LOW));
+        Notification n = mock(Notification.class);
+        StatusBarNotification sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+        when(notification_call.isConversation()).thenReturn(false);
+        when(notification_call.getNotification()).thenReturn(n);
+        when(notification_call.getSbn()).thenReturn(sbn);
+        when(sbn.getNotification()).thenReturn(n);
+        when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification_call)).isNull();
+
+        NotificationRecord notification_colorFg = spy(getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_LOW));
+        sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
+        n = mock(Notification.class);
+        when(notification_colorFg.isConversation()).thenReturn(false);
+        when(notification_colorFg.getNotification()).thenReturn(n);
+        when(notification_colorFg.getSbn()).thenReturn(sbn);
+        when(sbn.getNotification()).thenReturn(n);
+        when(n.isForegroundService()).thenReturn(true);
+        when(n.isColorized()).thenReturn(true);
+        when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+        assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+    @DisableFlags(FLAG_SORT_SECTION_BY_TIME)
+    public void testConversationGroupSections_disableSortSectionByTime() {
+        // Check that there are separate sections for conversations: alerting and silent
+        NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+                mUser, "", false, IMPORTANCE_LOW);
+        notification_conversation_silent = spy(notification_conversation_silent);
+        when(notification_conversation_silent.isConversation()).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+                "PeopleSection(silent)");
+
+        // Check that there is a correct section for conversations
+        NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+                mUser, "", false, IMPORTANCE_DEFAULT);
+        notification_conversation_alerting = spy(notification_conversation_alerting);
+        when(notification_conversation_alerting.isConversation()).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+                "PeopleSection(alerting)");
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS,
+            FLAG_SORT_SECTION_BY_TIME})
+    public void testConversationGroupSections() {
+        // Check that there is a single section for silent/alerting conversations
+        NotificationRecord notification_conversation_silent = getNotificationRecord(mPkg, 0, "",
+                mUser, "", false, IMPORTANCE_LOW);
+        notification_conversation_silent = spy(notification_conversation_silent);
+        when(notification_conversation_silent.isConversation()).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification_conversation_silent).mName).isEqualTo(
+                "PeopleSection");
+
+        NotificationRecord notification_conversation_alerting = getNotificationRecord(mPkg, 0, "",
+                mUser, "", false, IMPORTANCE_DEFAULT);
+        notification_conversation_alerting = spy(notification_conversation_alerting);
+        when(notification_conversation_alerting.isConversation()).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification_conversation_alerting).mName).isEqualTo(
+                "PeopleSection");
+
+        // Check that there is a section for priority conversations
+        NotificationRecord notification_conversation_prio = getNotificationRecord(mPkg, 0, "",
+                mUser, "", false, IMPORTANCE_DEFAULT);
+        notification_conversation_prio = spy(notification_conversation_prio);
+        when(notification_conversation_prio.isConversation()).thenReturn(true);
+        notification_conversation_prio.getChannel().setImportantConversation(true);
+        assertThat(GroupHelper.getSection(notification_conversation_prio).mName).isEqualTo(
+                "PeopleSection(priority)");
     }
 
 }
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 5a8de58..0a52238 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3047,6 +3047,41 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
+    public void testMultipleCancelOfLifetimeExtendedSendsOneUpdate() throws Exception {
+        final NotificationRecord notif = generateNotificationRecord(null);
+        notif.getSbn().getNotification().flags =
+                Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        mService.addNotification(notif);
+        final StatusBarNotification sbn = notif.getSbn();
+
+        assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1);
+        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+
+        // Send two cancelations.
+        mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
+                sbn.getUserId());
+        waitForIdle();
+        mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
+                sbn.getUserId());
+        waitForIdle();
+
+        assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1);
+        assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+
+        // Checks that only one post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testCancelAllClearsLifetimeExtended() throws Exception {
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
@@ -6419,12 +6454,31 @@
     @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        // Marks the notification as having already been lifetime extended and canceled.
         r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        r.setCanceledAfterLifetimeExtension(true);
+        r.setPostSilently(true);
         mService.addNotification(r);
 
         mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
         waitForIdle();
 
+        // At the moment prepareNotifyPostedLocked is called on the listeners,
+        // verify that FLAG_ONLY_ALERT_ONCE and shouldPostSilently are set, regardless of initial
+        // values.
+        doAnswer(
+                invocation -> {
+                    int flags = ((NotificationRecord) invocation.getArgument(0))
+                            .getSbn().getNotification().flags;
+                    assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
+                    boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0))
+                            .shouldPostSilently();
+                    assertThat(shouldPostSilently).isTrue();
+                    return null;
+                }
+        ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+        // Checks that the record gets marked as a direct reply having occurred.
         assertThat(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied())
                 .isTrue();
         // Checks that a post update is sent.
@@ -6437,9 +6491,65 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        // FLAG_ONLY_ALERT_ONCE was not present on the original notification, so it's not here.
         assertThat(captor.getValue().getNotification().flags
-                & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(0);
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
+        assertThat(captor.getValue().isCanceledAfterLifetimeExtension()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
+    public void testStats_DirectReplyLifetimeExtendedPostsUpdate_RestorePostSilently()
+            throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        // Marks the notification as having already been lifetime extended and canceled.
+        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        r.setPostSilently(false);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
+        waitForIdle();
+
+        // Checks that a post update is sent with shouldPostSilently set to true.
+        doAnswer(
+                invocation -> {
+                    boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0))
+                            .shouldPostSilently();
+                    assertThat(shouldPostSilently).isTrue();
+                    return null;
+                }
+        ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+        // Checks that shouldPostSilently is restored to its false state afterward.
+        assertThat(mService.getNotificationRecord(r.getKey()).shouldPostSilently()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
+    public void testStats_DirectReplyLifetimeExtendedPostsUpdate_RestoreOnlyAlertOnceFlag()
+            throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        // Marks the notification as having already been lifetime extended and canceled.
+        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
+        waitForIdle();
+
+        // Checks that a post update is sent with FLAG_ONLY_ALERT_ONCE set to true.
+        doAnswer(
+                invocation -> {
+                    int flags = ((NotificationRecord) invocation.getArgument(0))
+                            .getSbn().getNotification().flags;
+                    assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
+                    return null;
+                }
+        ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+        // Checks that the flag is removed afterward.
+        assertThat(mService.getNotificationRecord(r.getKey()).getSbn().getNotification().flags
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(0);
     }
 
     @Test
@@ -6476,6 +6586,7 @@
                 anyBoolean());
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
+        assertThat(captor.getValue().isCanceledAfterLifetimeExtension()).isFalse();
         assertThat(captor.getValue()
                 .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE).toString())
                 .isEqualTo("new title");
@@ -9143,11 +9254,13 @@
         final int replyIndex = 2;
         final String reply = "Hello";
         final boolean modifiedBeforeSending = true;
-        final boolean generatedByAssistant = true;
 
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
-        r.setSuggestionsGeneratedByAssistant(generatedByAssistant);
+        r.getSbn().getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+        r.setSuggestionsGeneratedByAssistant(true);
+        r.setCanceledAfterLifetimeExtension(true);
+        r.setPostSilently(true);
         mService.addNotification(r);
 
         mService.mNotificationDelegate.onNotificationSmartReplySent(
@@ -9155,6 +9268,21 @@
                 modifiedBeforeSending);
         waitForIdle();
 
+        // At the moment prepareNotifyPostedLocked is called on the listeners,
+        // verify that FLAG_ONLY_ALERT_ONCE and shouldPostSilently are set, regardless of initial
+        // values.
+        doAnswer(
+                invocation -> {
+                    int flags = ((NotificationRecord) invocation.getArgument(0))
+                            .getSbn().getNotification().flags;
+                    assertThat(flags & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
+                    boolean shouldPostSilently = ((NotificationRecord) invocation.getArgument(0))
+                            .shouldPostSilently();
+                    assertThat(shouldPostSilently).isTrue();
+                    return null;
+                }
+        ).when(mListeners).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
         // Checks that a post update is sent.
         verify(mWorkerHandler, times(1))
                 .post(any(NotificationManagerService.PostNotificationRunnable.class));
@@ -9165,8 +9293,10 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        // Flag was present before, so it's set afterward
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
+        // Should post silently was set before, so it's set afterward.
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f10370..c1e3f47 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2186,6 +2186,80 @@
     }
 
     @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void testReadXml_upgradeToModesUi_resetsImplicitRuleIcon() throws Exception {
+        setupZenConfig();
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+
+        // Add one implicit rule in the pre-MODES_UI configuration.
+        ZenRule implicitRuleBeforeModesUi = implicitRuleWithModesUi.copy();
+        implicitRuleBeforeModesUi.iconResName = "pkg_icon";
+        mZenModeHelper.mConfig.automaticRules.put(implicitRuleBeforeModesUi.id,
+                implicitRuleBeforeModesUi);
+        // Plus one other normal rule.
+        ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+        anotherRule.id = "other_rule";
+        anotherRule.iconResName = "other_icon";
+        anotherRule.type = TYPE_IMMERSIVE;
+        mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+        // Write with pre-modes-ui = (modes_api) version, then re-read.
+        ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_API);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // Implicit rule was updated.
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleBeforeModesUi.id))
+                .isEqualTo(implicitRuleWithModesUi);
+
+        // The other rule was untouched.
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+                .isEqualTo(anotherRule);
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void testReadXml_onModesUi_implicitRulesUntouched() throws Exception {
+        setupZenConfig();
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        // Add one implicit rule already in its post-modes-UI configuration, also customized with
+        // an icon;
+        ZenRule implicitRuleWithModesUi = expectedImplicitRule("pkg",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, POLICY, null);
+        implicitRuleWithModesUi.iconResName = "icon_chosen_by_user";
+        mZenModeHelper.mConfig.automaticRules.put(implicitRuleWithModesUi.id,
+                implicitRuleWithModesUi);
+
+        // Plus one other normal rule.
+        ZenRule anotherRule = newZenRule("other_pkg", Instant.now(), null);
+        anotherRule.id = "other_rule";
+        anotherRule.iconResName = "other_icon";
+        anotherRule.type = TYPE_IMMERSIVE;
+        mZenModeHelper.mConfig.automaticRules.put(anotherRule.id, anotherRule);
+
+        // Write with modes_ui version, then re-read.
+        ByteArrayOutputStream baos = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // Both rules were untouched
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleWithModesUi.id))
+                .isEqualTo(implicitRuleWithModesUi);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get(anotherRule.id))
+                .isEqualTo(anotherRule);
+    }
+
+    @Test
     public void testCountdownConditionSubscription() throws Exception {
         ZenModeConfig config = new ZenModeConfig();
         mZenModeHelper.mConfig = config;
@@ -6852,7 +6926,9 @@
         rule.zenPolicy = policy;
         rule.pkg = ownerPkg;
         rule.name = CUSTOM_APP_LABEL;
-        rule.iconResName = ICON_RES_NAME;
+        if (!Flags.modesUi()) {
+            rule.iconResName = ICON_RES_NAME;
+        }
         rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
                 CUSTOM_APP_LABEL);
         rule.type = AutomaticZenRule.TYPE_OTHER;
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 43ad44f..2549ff5 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,11 +32,11 @@
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
-        "junit-params",
         "mockito-target-inline-minus-junit4",
         "platform-test-annotations",
         "service-permission.stubs.system_server",
         "services.core",
+        "TestParameterInjector",
     ],
     jni_libs: ["libdexmakerjvmtiagent"],
     platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index e0d05df..2d312d2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -18,32 +18,36 @@
 
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
 import static android.os.VibrationEffect.EFFECT_CLICK;
+import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED;
+import static android.os.vibrator.Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES;
 
 import static com.android.internal.R.xml.haptic_feedback_customization;
-import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_rotary_encoder;
+import static com.android.internal.R.xml.haptic_feedback_customization_source_touchscreen;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AtomicFile;
-import android.util.SparseArray;
+import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
-import com.android.internal.annotations.Keep;
 
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -56,7 +60,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 
-@RunWith(JUnitParamsRunner.class)
+@RunWith(TestParameterInjector.class)
 public class HapticFeedbackCustomizationTest {
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -69,166 +73,173 @@
     private static final VibrationEffect COMPOSITION_VIBRATION =
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
 
-    private static final String PREDEFINED_VIBRATION_XML =
+    private static final String PREDEFINED_VIBRATION_CLICK_XML =
             "<vibration-effect><predefined-effect name=\"click\"/></vibration-effect>";
-    private static final VibrationEffect PREDEFINED_VIBRATION =
+    private static final VibrationEffect PREDEFINED_VIBRATION_CLICK =
             VibrationEffect.createPredefined(EFFECT_CLICK);
 
+    private static final String PREDEFINED_VIBRATION_TICK_XML =
+            "<vibration-effect><predefined-effect name=\"tick\"/></vibration-effect>";
+    private static final VibrationEffect PREDEFINED_VIBRATION_TICK =
+            VibrationEffect.createPredefined(EFFECT_TICK);
+
     private static final String WAVEFORM_VIBRATION_XML = "<vibration-effect>"
             + "<waveform-effect>"
             + "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
             + "</waveform-effect>"
             + "</vibration-effect>";
-    private static final VibrationEffect WAVEFORM_VIBARTION =
+    private static final VibrationEffect WAVEFORM_VIBRATION =
             VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
 
     @Mock private Resources mResourcesMock;
     @Mock private VibratorInfo mVibratorInfoMock;
 
-    @Keep
-    private static Object[][] hapticFeedbackCustomizationTestArguments() {
-        // (boolean hasConfigFile, boolean hasRes).
-        return new Object[][] {{true, true}, {true, false}, {false, true}};
+    private enum CustomizationSource {
+        DEVICE_CONFIG_FILE,
+        DEVICE_RESOURCE,
+        DEVICE_RESOURCE_INPUT_ROTARY,
+        DEVICE_RESOURCE_INPUT_TOUCHSCREEN
     }
 
     @Before
     public void setUp() {
+        clearFileAndResourceSetup();
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
-        mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
-        mSetFlagsRule.disableFlags(
-                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_noCustomization_success(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-        setupParseCustomizations(xml, hasConfigFile, hasRes);
-
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_featureFlagDisabled_returnsNull(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+    public void testParseCustomizations_featureFlagDisabled_customizationNotLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
         // Valid customization XML.
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
 
-        setupParseCustomizations(xml, hasConfigFile, hasRes);
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
                 .isNull();
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
     public void testParseCustomizations_oneVibrationCustomization_success(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xml = "<haptic-feedback-constants>"
-                + "<constant id=\"10\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-        expectedMapping.put(10, COMPOSITION_VIBRATION);
-
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_oneVibrationSelectCustomization_success(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xml = "<haptic-feedback-constants>"
-                + "<constant id=\"10\">"
-                + "<vibration-select>"
-                + COMPOSITION_VIBRATION_XML
-                + "</vibration-select>"
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-        expectedMapping.put(10, COMPOSITION_VIBRATION);
-
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_multipleCustomizations_success(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xml = "<haptic-feedback-constants>"
-                + "<constant id=\"1\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "<constant id=\"12\">"
-                + "<vibration-select>"
-                + PREDEFINED_VIBRATION_XML
-                + WAVEFORM_VIBRATION_XML
-                + "</vibration-select>"
-                + "</constant>"
-                + "<constant id=\"150\">"
-                + PREDEFINED_VIBRATION_XML
-                + "</constant>"
-                + "<constant id=\"10\">"
-                + "<vibration-select>"
-                + WAVEFORM_VIBRATION_XML
-                + COMPOSITION_VIBRATION_XML
-                + "</vibration-select>"
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-        expectedMapping.put(1, COMPOSITION_VIBRATION);
-        expectedMapping.put(12, PREDEFINED_VIBRATION);
-        expectedMapping.put(150, PREDEFINED_VIBRATION);
-        expectedMapping.put(10, WAVEFORM_VIBARTION);
-
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
-            boolean hasConfigFile, boolean hasRes)
-                throws Exception {
-        makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
-        String xml = "<haptic-feedback-constants>"
-                + "<constant id=\"1\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "<constant id=\"12\">"
-                + "<vibration-select>"
-                + PREDEFINED_VIBRATION_XML
-                + WAVEFORM_VIBRATION_XML
-                + "</vibration-select>"
-                + "</constant>"
-                + "<constant id=\"150\">"
-                + PREDEFINED_VIBRATION_XML
-                + "</constant>"
-                + "<constant id=\"10\">"
-                + "<vibration-select>"
-                + WAVEFORM_VIBRATION_XML
-                + COMPOSITION_VIBRATION_XML
-                + "</vibration-select>"
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
-            boolean hasConfigFile, boolean hasRes)
+            @TestParameter CustomizationSource customizationSource)
             throws Exception {
-        makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
+
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+                .isEqualTo(COMPOSITION_VIBRATION);
+    }
+
+    @Test
+    public void testParseCustomizations_oneVibrationSelectCustomization_success(
+            @TestParameter CustomizationSource customizationSource)
+            throws Exception {
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<vibration-select>"
+                + COMPOSITION_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
+
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+                .isEqualTo(COMPOSITION_VIBRATION);
+    }
+
+    @Test
+    public void testParseCustomizations_multipleCustomizations_success(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"1\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"12\">"
+                + "<vibration-select>"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + WAVEFORM_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "<constant id=\"150\">"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + "</constant>"
+                + "<constant id=\"10\">"
+                + "<vibration-select>"
+                + WAVEFORM_VIBRATION_XML
+                + COMPOSITION_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
+
+        assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+                customization))
+                .isEqualTo(COMPOSITION_VIBRATION);
+        assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+                customization))
+                .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+        assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+                customization))
+                .isEqualTo(PREDEFINED_VIBRATION_CLICK);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customization))
+                .isEqualTo(WAVEFORM_VIBRATION);
+    }
+
+    @Test
+    public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"1\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"12\">"
+                + "<vibration-select>"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + WAVEFORM_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "<constant id=\"150\">"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + "</constant>"
+                + "<constant id=\"10\">"
+                + "<vibration-select>"
+                + WAVEFORM_VIBRATION_XML
+                + COMPOSITION_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
+
+        assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+                customization)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+                customization)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+                customization)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customization)).isNull();
+    }
+
+    @Test
+    public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        makeSupported(PREDEFINED_VIBRATION_CLICK, WAVEFORM_VIBRATION);
         makeUnsupported(COMPOSITION_VIBRATION);
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"1\">" // No supported customization.
@@ -236,68 +247,89 @@
                 + "</constant>"
                 + "<constant id=\"12\">" // PREDEFINED_VIBRATION is the first/only supported.
                 + "<vibration-select>"
-                + PREDEFINED_VIBRATION_XML
+                + PREDEFINED_VIBRATION_CLICK_XML
                 + COMPOSITION_VIBRATION_XML
                 + "</vibration-select>"
                 + "</constant>"
-                + "<constant id=\"14\">" // WAVEFORM_VIBARTION is the first/only supported.
+                + "<constant id=\"14\">" // WAVEFORM_VIBRATION is the first/only supported.
                 + "<vibration-select>"
                 + COMPOSITION_VIBRATION_XML
                 + WAVEFORM_VIBRATION_XML
                 + "</vibration-select>"
                 + "</constant>"
                 + "<constant id=\"150\">" // PREDEFINED_VIBRATION is the first/only supported.
-                + PREDEFINED_VIBRATION_XML
+                + PREDEFINED_VIBRATION_CLICK_XML
                 + "</constant>"
                 + "<constant id=\"10\">" // PREDEFINED_VIBRATION is the first supported.
                 + "<vibration-select>"
-                + PREDEFINED_VIBRATION_XML
+                + PREDEFINED_VIBRATION_CLICK_XML
                 + WAVEFORM_VIBRATION_XML
                 + "</vibration-select>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
-        expectedMapping.put(12, PREDEFINED_VIBRATION);
-        expectedMapping.put(14, WAVEFORM_VIBARTION);
-        expectedMapping.put(150, PREDEFINED_VIBRATION);
-        expectedMapping.put(10, PREDEFINED_VIBRATION);
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
 
-        assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 1, customizationSource,
+                customization)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 12, customizationSource,
+                customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+        assertThat(getEffectForSource(/* effectId= */ 14, customizationSource,
+                customization)).isEqualTo(WAVEFORM_VIBRATION);
+        assertThat(getEffectForSource(/* effectId= */ 150, customizationSource,
+                customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customization)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
     }
 
     @Test
-    public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
-        setCustomizationFilePath("");
+    public void testParseCustomizations_malformedXml_notLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        // No end "<constant>" tag
+        String xmlNoEndConstantTag = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customizationNoEndConstantTag = createCustomizationForSource(
+                xmlNoEndConstantTag, customizationSource);
+        // No start "<haptic-feedback-constants>" tag
+        String xmlNoStartCustomizationTag = "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationNoStartCustomizationTag =
+                createCustomizationForSource(xmlNoStartCustomizationTag, customizationSource);
+        // No end "<haptic-feedback-constants>" tag
+        String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>";
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationNoEndCustomizationTag =
+                createCustomizationForSource(xmlNoEndCustomizationTag, customizationSource);
+        // No start "<constant>" tag
+        String xmlNoStartConstantTag = "<haptic-feedback-constants>"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationNoStartConstantTag = createCustomizationForSource(
+                xmlNoStartConstantTag, customizationSource);
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
-                .isNull();
-
-        setCustomizationFilePath(null);
-
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
-                .isNull();
-
-        setCustomizationFilePath("non_existent_file.xml");
-
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
-                .isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationNoEndConstantTag)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationNoStartCustomizationTag)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationNoEndCustomizationTag)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationNoStartConstantTag)).isNull();
     }
 
     @Test
-    public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
-        mSetFlagsRule.enableFlags(
-                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
-        doThrow(new Resources.NotFoundException())
-                .when(mResourcesMock).getXml(haptic_feedback_customization);
-
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
-                .isNull();
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
+    public void testParseCustomizations_disallowedVibrationForHapticFeedback_notLoaded(
+                @TestParameter CustomizationSource customizationSource) throws Exception {
         // The XML content is good, but the serialized vibration is not supported for haptic
         // feedback usage (i.e. repeating vibration).
         String xml = "<haptic-feedback-constants>"
@@ -311,185 +343,245 @@
                 + "</vibration-effect>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
 
-        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+                .isNull();
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_emptyXml_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        assertParseCustomizationsFails("", hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_noVibrationXml_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
+    public void testParseCustomizations_xmlNoVibration_notLoaded(
+                @TestParameter CustomizationSource customizationSource) throws Exception {
         String xml = "<haptic-feedback-constants>"
                 + "<constant id=\"1\">"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xml,
+                customizationSource);
 
-        assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 1, customizationSource, customization))
+                .isNull();
     }
 
+
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_badEffectId_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        // Negative id
+    public void testParseCustomizations_badEffectId_notLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
         String xmlNegativeId = "<haptic-feedback-constants>"
                 + "<constant id=\"-10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        // Non-numeral id
-        String xmlNonNumericalId = "<haptic-feedback-constants>"
-                + "<constant id=\"xyz\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(
+                xmlNegativeId, customizationSource);
 
-        assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ -10, customizationSource, customization))
+                .isNull();
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_malformedXml_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        // No start "<constant>" tag
-        String xmlNoStartConstantTag = "<haptic-feedback-constants>"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        // No end "<constant>" tag
-        String xmlNoEndConstantTag = "<haptic-feedback-constants>"
-                + "<constant id=\"10\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</haptic-feedback-constants>";
-        // No start "<haptic-feedback-constants>" tag
-        String xmlNoStartCustomizationTag = "<constant id=\"10\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>"
-                + "</haptic-feedback-constants>";
-        // No end "<haptic-feedback-constants>" tag
-        String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
-                + "<constant id=\"10\">"
-                + COMPOSITION_VIBRATION_XML
-                + "</constant>";
-
-        assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
-    }
-
-    @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_badVibrationXml_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xmlBad1 = "<haptic-feedback-constants>"
+    public void testParseCustomizations_badVibrationXml_notLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        // Case#1 - bad opening tag <bad-vibration-effect>
+        String xmlBadTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<bad-vibration-effect></bad-vibration-effect>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        String xmlBad2 = "<haptic-feedback-constants>"
+        HapticFeedbackCustomization customizationBadTag = createCustomizationForSource(
+                xmlBadTag, customizationSource);
+        // Case#2 - bad attribute "name" for tag <predefined-effect>
+        String xmlBadEffectName = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        String xmlBad3 = "<haptic-feedback-constants>"
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationBadEffectName = createCustomizationForSource(
+                xmlBadEffectName, customizationSource);
+        // Case#3 - miss "</vibration-select>"
+        String xmlBadEffectNameAndMissingCloseTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-select>"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        String xmlBad4 = "<haptic-feedback-constants>"
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationBadEffectNameAndMissingCloseTag =
+                createCustomizationForSource(xmlBadEffectNameAndMissingCloseTag,
+                        customizationSource);
+        // Case#4 - miss "<vibration-select>"
+        String xmlBadEffectNameAndMissingOpenTag = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
                 + "</vibration-select>"
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationBadEffectNameAndMissingOpenTag =
+                createCustomizationForSource(xmlBadEffectNameAndMissingOpenTag,
+                        customizationSource);
 
-        assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationBadTag)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationBadEffectName)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationBadEffectNameAndMissingCloseTag)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationBadEffectNameAndMissingOpenTag)).isNull();
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_badConstantAttribute_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
-        String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
+    public void testParseCustomizations_badConstantAttribute_notLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
+        // Case#1 - bad attribute id for tag <constant>
+        String xmlBadConstantIdAttribute = "<haptic-feedback-constants>"
                 + "<constant iddddd=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
-        String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
+        HapticFeedbackCustomization customizationBadConstantIdAttribute =
+                createCustomizationForSource(xmlBadConstantIdAttribute, customizationSource);
+        // Case#2 - unexpected attribute "unwanted" for tag <constant>
+        String xmlUnwantedConstantAttribute = "<haptic-feedback-constants>"
                 + "<constant id=\"10\" unwanted-attr=\"1\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        clearFileAndResourceSetup();
+        HapticFeedbackCustomization customizationUnwantedConstantAttribute =
+                createCustomizationForSource(xmlUnwantedConstantAttribute, customizationSource);
 
-        assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
-        assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationBadConstantIdAttribute)).isNull();
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource,
+                customizationUnwantedConstantAttribute)).isNull();
     }
 
     @Test
-    @Parameters(method = "hapticFeedbackCustomizationTestArguments")
-    public void testParseCustomizations_duplicateEffects_throwsException(
-            boolean hasConfigFile, boolean hasRes) throws Exception {
+    public void testParseCustomizations_duplicateEffects_notLoaded(
+            @TestParameter CustomizationSource customizationSource) throws Exception {
         String xmlDuplicateEffect = "<haptic-feedback-constants>"
                 + "<constant id=\"10\">"
                 + COMPOSITION_VIBRATION_XML
                 + "</constant>"
                 + "<constant id=\"10\">"
-                + PREDEFINED_VIBRATION_XML
+                + PREDEFINED_VIBRATION_CLICK_XML
                 + "</constant>"
                 + "<constant id=\"11\">"
-                + PREDEFINED_VIBRATION_XML
+                + PREDEFINED_VIBRATION_CLICK_XML
                 + "</constant>"
                 + "</haptic-feedback-constants>";
+        HapticFeedbackCustomization customization = createCustomizationForSource(xmlDuplicateEffect,
+                customizationSource);
 
-        assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
+        assertThat(getEffectForSource(/* effectId= */ 10, customizationSource, customization))
+                .isNull();
+        assertThat(getEffectForSource(/* effectId= */ 11, customizationSource, customization))
+                .isNull();
     }
 
-    private void assertParseCustomizationsSucceeds(String xml,
-            SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
-            boolean hasRes) throws Exception {
-        setupParseCustomizations(xml, hasConfigFile, hasRes);
-        assertThat(expectedCustomizations.contentEquals(
-                HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
-                .isTrue();
-    }
-
-    private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+    @Test
+    public void testParseCustomizations_withDifferentCustomizations_loadsCorrectOne()
             throws Exception {
-        setupParseCustomizations(xml, hasConfigFile, hasRes);
-        assertThrows("Expected haptic feedback customization to fail",
-                CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(
-                        mResourcesMock, mVibratorInfoMock));
+        String xmlBaseCustomization = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "<constant id=\"14\">"
+                + "<vibration-select>"
+                + WAVEFORM_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        String xmlRotaryInputCustomization = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<vibration-select>"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + COMPOSITION_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        String xmlTouchScreenInputCustomization = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + PREDEFINED_VIBRATION_TICK_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizations(xmlBaseCustomization, CustomizationSource.DEVICE_RESOURCE);
+        setupCustomizations(xmlRotaryInputCustomization,
+                CustomizationSource.DEVICE_RESOURCE_INPUT_ROTARY);
+        HapticFeedbackCustomization customization = createCustomizationForSource(
+                xmlTouchScreenInputCustomization,
+                CustomizationSource.DEVICE_RESOURCE_INPUT_TOUCHSCREEN);
+
+        // Matching customizations.
+        assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+        assertThat(customization.getEffect(/* effectId= */ 14)).isEqualTo(WAVEFORM_VIBRATION);
+        assertThat(customization.getEffect(/* effectId= */ 10,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PREDEFINED_VIBRATION_CLICK);
+        assertThat(customization.getEffect(/* effectId= */ 10,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PREDEFINED_VIBRATION_TICK);
+        // Missing from input source customization xml. Fallback to base.
+        assertThat(customization.getEffect(/* effectId= */ 14,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(WAVEFORM_VIBRATION);
+        assertThat(customization.getEffect(/* effectId= */ 14,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(WAVEFORM_VIBRATION);
     }
 
-    private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+    @Test
+    public void testParseCustomizations_customizationsFromConfigFileAndRes_preferConfigFile()
             throws Exception {
-        clearFileAndResourceSetup();
-        if (hasConfigFile) {
-            setupCustomizationFile(xml);
-        }
-        if (hasRes) {
-            setupCustomizationResource(xml);
+        String xmlConfigFileCustomization = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + COMPOSITION_VIBRATION_XML
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        String xmlResourceCustomization = "<haptic-feedback-constants>"
+                + "<constant id=\"10\">"
+                + "<vibration-select>"
+                + PREDEFINED_VIBRATION_CLICK_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "<constant id=\"14\">"
+                + "<vibration-select>"
+                + WAVEFORM_VIBRATION_XML
+                + "</vibration-select>"
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizations(xmlConfigFileCustomization, CustomizationSource.DEVICE_CONFIG_FILE);
+        HapticFeedbackCustomization customization = createCustomizationForSource(
+                xmlResourceCustomization, CustomizationSource.DEVICE_RESOURCE);
+
+        // When config file and resource customizations are both available. Load the config file
+        // Customization.
+        assertThat(customization.getEffect(/* effectId= */ 10)).isEqualTo(COMPOSITION_VIBRATION);
+        assertThat(customization.getEffect(/* effectId= */ 14)).isNull();
+    }
+
+    private HapticFeedbackCustomization createCustomizationForSource(String xml,
+            CustomizationSource customizationSource) throws Exception {
+        setupCustomizations(xml, customizationSource);
+        return new HapticFeedbackCustomization(mResourcesMock, mVibratorInfoMock);
+    }
+
+    private void setupCustomizations(String xml, CustomizationSource customizationSource)
+            throws Exception {
+        switch (customizationSource) {
+            case DEVICE_CONFIG_FILE -> setupCustomizationFile(xml);
+            case DEVICE_RESOURCE -> setupCustomizationResource(xml, haptic_feedback_customization);
+            case DEVICE_RESOURCE_INPUT_ROTARY -> setupCustomizationResource(xml,
+                    haptic_feedback_customization_source_rotary_encoder);
+            case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> setupCustomizationResource(xml,
+                    haptic_feedback_customization_source_touchscreen);
         }
     }
 
-    private void clearFileAndResourceSetup() {
-        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
-                .thenReturn(null);
-        when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+    private void setupCustomizationResource(String xml, int xmlResId) throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        mSetFlagsRule.enableFlags(FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+        doReturn(FakeXmlResourceParser.fromXml(xml)).when(mResourcesMock).getXml(xmlResId);
     }
 
     private void setupCustomizationFile(String xml) throws Exception {
@@ -498,15 +590,34 @@
     }
 
     private void setCustomizationFilePath(String path) {
-        when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
-                .thenReturn(path);
+        doReturn(path).when(mResourcesMock)
+                .getString(R.string.config_hapticFeedbackCustomizationFile);
     }
 
-    private void setupCustomizationResource(String xml) throws Exception {
-        mSetFlagsRule.enableFlags(
-                Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
-        when(mResourcesMock.getXml(haptic_feedback_customization))
-                .thenReturn(FakeXmlResourceParser.fromXml(xml));
+    private void clearFileAndResourceSetup() {
+        doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+                .getString(R.string.config_hapticFeedbackCustomizationFile);
+        doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+                .getXml(haptic_feedback_customization);
+        doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+                .getXml(haptic_feedback_customization_source_rotary_encoder);
+        doThrow(new Resources.NotFoundException()).when(mResourcesMock)
+                .getXml(haptic_feedback_customization_source_touchscreen);
+    }
+
+    @Nullable
+    private VibrationEffect getEffectForSource(int effectId,
+            CustomizationSource customizationSource,
+            HapticFeedbackCustomization hapticFeedbackCustomization) {
+        return switch (customizationSource) {
+            case DEVICE_CONFIG_FILE, DEVICE_RESOURCE -> hapticFeedbackCustomization.getEffect(
+                    effectId);
+            case DEVICE_RESOURCE_INPUT_ROTARY -> hapticFeedbackCustomization.getEffect(effectId,
+                    InputDevice.SOURCE_ROTARY_ENCODER);
+            case DEVICE_RESOURCE_INPUT_TOUCHSCREEN -> hapticFeedbackCustomization.getEffect(
+                    effectId,
+                    InputDevice.SOURCE_TOUCHSCREEN);
+        };
     }
 
     private void makeSupported(VibrationEffect... effects) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 8797e63..6076d33 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -21,16 +21,21 @@
 import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
 import static android.os.VibrationAttributes.USAGE_TOUCH;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
 import static android.os.VibrationEffect.EFFECT_CLICK;
 import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
 import static android.os.VibrationEffect.EFFECT_TICK;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
 import static android.view.HapticFeedbackConstants.BIOMETRIC_CONFIRM;
 import static android.view.HapticFeedbackConstants.BIOMETRIC_REJECT;
 import static android.view.HapticFeedbackConstants.CLOCK_TICK;
 import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.DRAG_START;
 import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
 import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
+import static android.view.HapticFeedbackConstants.NO_HAPTICS;
 import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
 import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
 import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
@@ -42,6 +47,7 @@
 
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
@@ -52,11 +58,13 @@
 import android.util.AtomicFile;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -75,6 +83,11 @@
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
     private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
+    private static final VibrationEffect PRIMITIVE_THUD_EFFECT =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_THUD, 0.5497f).compose();
+    private static final VibrationEffect PRIMITIVE_QUICK_RISE_EFFECT =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_QUICK_RISE,
+                    0.6497f).compose();
 
     private static final int[] SCROLL_FEEDBACK_CONSTANTS =
             new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
@@ -90,45 +103,52 @@
 
     @Mock private Resources mResourcesMock;
 
-    @Test
-    public void testNonExistentCustomization_useDefault() throws Exception {
-        // No customization file is set.
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
-                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
-
-        // The customization file specifies no customization.
-        setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
-        hapticProvider = createProviderWithDefaultCustomizations();
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
-                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+    @Before
+    public void setUp() {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
     }
 
     @Test
-    public void testExceptionParsingCustomizations_useDefault() throws Exception {
-        setupCustomizationFile("<bad-xml></bad-xml>");
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+    public void testNonExistentCustomization_useDefault() {
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
-                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
+        // No customization for `CLOCK_TICK`, so the default vibration is used.
+        assertThat(provider.getVibration(CLOCK_TICK)).isEqualTo(
+                VibrationEffect.get(EFFECT_TEXTURE_TICK));
+        assertThat(provider.getVibration(CLOCK_TICK,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+                VibrationEffect.get(EFFECT_TEXTURE_TICK));
+        assertThat(provider.getVibration(CLOCK_TICK, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
     }
 
     @Test
-    public void testUseValidCustomizedVibration() throws Exception {
-        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+    public void testUseValidCustomizedVibration() {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+                PRIMITIVE_QUICK_RISE);
         SparseArray<VibrationEffect> customizations = new SparseArray<>();
         customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+        SparseArray<VibrationEffect> customizationsRotary = new SparseArray<>();
+        customizationsRotary.put(CONTEXT_CLICK, PRIMITIVE_TICK_EFFECT);
+        customizationsRotary.put(DRAG_START, PRIMITIVE_QUICK_RISE_EFFECT);
+        SparseArray<VibrationEffect> customizationsTouchScreen = new SparseArray<>();
+        customizationsTouchScreen.put(CONTEXT_CLICK, PRIMITIVE_THUD_EFFECT);
+        customizationsTouchScreen.put(DRAG_START, PRIMITIVE_CLICK_EFFECT);
+        HapticFeedbackVibrationProvider provider = createProvider(customizations,
+                customizationsRotary, customizationsTouchScreen);
 
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
-        // The override for `CONTEXT_CLICK` is used.
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+        // The customization for `CONTEXT_CLICK`.
+        assertThat(provider.getVibration(CONTEXT_CLICK))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
-        // `CLOCK_TICK` has no override, so the default vibration is used.
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
-                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
+        assertThat(provider.getVibration(CONTEXT_CLICK,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+        assertThat(provider.getVibration(CONTEXT_CLICK,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_THUD_EFFECT);
+        // The customization for `DRAG_START`.
+        assertThat(provider.getVibration(DRAG_START,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+        assertThat(provider.getVibration(DRAG_START,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
     }
 
     @Test
@@ -140,92 +160,151 @@
                 + "</constant>"
                 + "</haptic-feedback-constants>";
         setupCustomizationFile(xml);
-
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         // The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
+        assertThat(provider.getVibration(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
         // `CLOCK_TICK` has no override, so the default vibration is used.
-        assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
+        assertThat(provider.getVibration(CLOCK_TICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
     }
 
     @Test
-    public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() throws Exception {
+    public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() {
         mockHapticTextSupport(false);
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
         SparseArray<VibrationEffect> customizations = new SparseArray<>();
         customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
+        HapticFeedbackVibrationProvider provider = createProvider(
+                /* customizations= */ customizations,
+                /* customizationsForRotary= */ customizations,
+                /* customizationsForTouchScreen= */ customizations);
 
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+        assertThat(
+                provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
 
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
-        hapticProvider = createProvider(/* customizations= */ null);
+        provider = createProviderWithoutCustomizations();
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isNull();
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+        assertThat(
+                provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
     }
 
     @Test
-    public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() throws Exception {
+    public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() {
         mockHapticTextSupport(true);
-        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
         SparseArray<VibrationEffect> customizations = new SparseArray<>();
         customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
-
+        SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+        customizationsByRotary.put(TEXT_HANDLE_MOVE, PRIMITIVE_TICK_EFFECT);
+        SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+        customizationsByTouchScreen.put(TEXT_HANDLE_MOVE, PRIMITIVE_THUD_EFFECT);
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+        HapticFeedbackVibrationProvider provider = createProvider(customizations,
+                customizationsByRotary, customizationsByTouchScreen);
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
-                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(PRIMITIVE_TICK_EFFECT);
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(PRIMITIVE_THUD_EFFECT);
 
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
-        hapticProvider = createProvider(/* customizations= */ null);
+        provider = createProviderWithoutCustomizations();
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE)).isEqualTo(
+                VibrationEffect.get(EFFECT_TEXTURE_TICK));
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+                VibrationEffect.get(EFFECT_TEXTURE_TICK));
+        assertThat(provider.getVibration(TEXT_HANDLE_MOVE, InputDevice.SOURCE_TOUCHSCREEN))
                 .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
     }
 
     @Test
-    public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource()
-                throws Exception {
-        mockSafeModeEnabledVibration(10, 20, 30, 40);
-        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
+    public void testFeedbackConstantNoHapticEffect_noVibrationRegardlessCustomizations() {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_THUD, PRIMITIVE_TICK);
         SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+        customizations.put(NO_HAPTICS, PRIMITIVE_CLICK_EFFECT);
+        SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+        customizationsByRotary.put(NO_HAPTICS, PRIMITIVE_TICK_EFFECT);
+        SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+        customizationsByTouchScreen.put(NO_HAPTICS, PRIMITIVE_THUD_EFFECT);
+        HapticFeedbackVibrationProvider provider = createProvider(customizations,
+                customizationsByRotary, customizationsByTouchScreen);
 
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
-                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
-
-        mockSafeModeEnabledVibration(null);
-        hapticProvider = createProvider(customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
-                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        // Whatever customization set to NO_HAPTICS, no vibration happens.
+        assertThat(provider.getVibration(NO_HAPTICS)).isNull();
+        assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_ROTARY_ENCODER)).isNull();
+        assertThat(provider.getVibration(NO_HAPTICS, InputDevice.SOURCE_TOUCHSCREEN)).isNull();
     }
 
     @Test
-    public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
-                throws Exception {
+    public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource() {
         mockSafeModeEnabledVibration(10, 20, 30, 40);
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD);
+        SparseArray<VibrationEffect> safeModeCustomizations = new SparseArray<>();
+        safeModeCustomizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
+        SparseArray<VibrationEffect> safeModeCustomizationsByRotary = new SparseArray<>();
+        safeModeCustomizationsByRotary.put(SAFE_MODE_ENABLED, PRIMITIVE_THUD_EFFECT);
+        SparseArray<VibrationEffect> safeModeCustomizationsByTouchScreen = new SparseArray<>();
+        safeModeCustomizationsByTouchScreen.put(SAFE_MODE_ENABLED, PRIMITIVE_TICK_EFFECT);
+        HapticFeedbackVibrationProvider provider =
+                createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+                        safeModeCustomizationsByTouchScreen);
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
-                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(PRIMITIVE_THUD_EFFECT);
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(PRIMITIVE_TICK_EFFECT);
+
+        // Resource changed
+        mockSafeModeEnabledVibration(null);
+        provider =
+                createProvider(safeModeCustomizations, safeModeCustomizationsByRotary,
+                        safeModeCustomizationsByTouchScreen);
+
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(PRIMITIVE_THUD_EFFECT);
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(PRIMITIVE_TICK_EFFECT);
     }
 
     @Test
-    public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
-                throws Exception {
-        mockSafeModeEnabledVibration(null);
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);
+    public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed() {
+        mockSafeModeEnabledVibration(10, 20, 30, 40);
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED))
+                .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(VibrationEffect.createWaveform(new long[]{10, 20, 30, 40}, -1));
+    }
+
+    @Test
+    public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed() {
+        mockSafeModeEnabledVibration(null);
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
+
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED)).isNull();
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isNull();
+        assertThat(provider.getVibration(SAFE_MODE_ENABLED, InputDevice.SOURCE_TOUCHSCREEN))
+                .isNull();
     }
 
     @Test
@@ -236,19 +315,25 @@
         customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
 
         // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
-        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+        HapticFeedbackVibrationProvider provider = createProvider(customizations);
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
-                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
-                .isEqualTo(PRIMITIVE_TICK_EFFECT);
+        assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(PRIMITIVE_CLICK_EFFECT);
+        assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(PRIMITIVE_TICK_EFFECT);
 
         // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
-        hapticProvider = createProviderWithDefaultCustomizations();
+        provider = createProviderWithoutCustomizations();
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+        assertThat(provider.getVibration(KEYBOARD_TAP))
                 .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+        assertThat(provider.getVibration(KEYBOARD_RELEASE))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+        assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+        assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+        assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
     }
 
@@ -256,25 +341,64 @@
     public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() {
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        assertThat(provider.getVibration(KEYBOARD_TAP)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+        assertThat(provider.getVibration(KEYBOARD_RELEASE)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+        assertThat(provider.getVibration(KEYBOARD_TAP,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+        assertThat(provider.getVibration(KEYBOARD_RELEASE,
+                InputDevice.SOURCE_ROTARY_ENCODER)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+        assertThat(provider.getVibration(KEYBOARD_TAP,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+        assertThat(provider.getVibration(KEYBOARD_RELEASE,
+                InputDevice.SOURCE_TOUCHSCREEN)).isEqualTo(
+                VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK,
+                        KEYBOARD_VIBRATION_FIXED_AMPLITUDE).compose());
+    }
 
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
-                .isEqualTo(VibrationEffect.startComposition()
-                        .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
-                        .compose());
-        assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
-                .isEqualTo(VibrationEffect.startComposition()
-                        .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
-                        .compose());
+    @Test
+    public void testKeyboardHaptic_withCustomizations_customEffectsUsed() {
+        mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+                PRIMITIVE_QUICK_RISE);
+        SparseArray<VibrationEffect> customizations = new SparseArray<>();
+        customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+        customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+        SparseArray<VibrationEffect> customizationsByRotary = new SparseArray<>();
+        customizationsByRotary.put(KEYBOARD_TAP, PRIMITIVE_THUD_EFFECT);
+        customizationsByRotary.put(KEYBOARD_RELEASE, PRIMITIVE_QUICK_RISE_EFFECT);
+        SparseArray<VibrationEffect> customizationsByTouchScreen = new SparseArray<>();
+        customizationsByTouchScreen.put(KEYBOARD_TAP, PRIMITIVE_QUICK_RISE_EFFECT);
+        customizationsByTouchScreen.put(KEYBOARD_RELEASE, PRIMITIVE_THUD_EFFECT);
+        HapticFeedbackVibrationProvider provider = createProvider(customizations,
+                customizationsByRotary, customizationsByTouchScreen);
+
+        assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(PRIMITIVE_THUD_EFFECT);
+        assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_ROTARY_ENCODER))
+                .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+        assertThat(provider.getVibration(KEYBOARD_TAP, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(PRIMITIVE_QUICK_RISE_EFFECT);
+        assertThat(provider.getVibration(KEYBOARD_RELEASE, InputDevice.SOURCE_TOUCHSCREEN))
+                .isEqualTo(PRIMITIVE_THUD_EFFECT);
     }
 
     @Test
     public void testVibrationAttribute_biometricConstants_returnsCommunicationRequestUsage() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+            VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                     effectId, /* flags */ 0, /* privFlags */ 0);
             assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
         }
@@ -282,9 +406,9 @@
 
     @Test
     public void testVibrationAttribute_forNotBypassingIntensitySettings() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
-        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+        VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                 SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
 
         assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
@@ -292,9 +416,9 @@
 
     @Test
     public void testVibrationAttribute_forByassingIntensitySettings() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
-        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+        VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                 SAFE_MODE_ENABLED,
                 /* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
 
@@ -304,10 +428,10 @@
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
         mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+            VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                     effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
@@ -317,10 +441,10 @@
     @Test
     public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
         mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+            VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                     effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                    .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
@@ -329,10 +453,10 @@
 
     @Test
     public void testVibrationAttribute_notIme_useTouchUsage() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+            VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                     effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
@@ -341,10 +465,10 @@
 
     @Test
     public void testVibrationAttribute_isIme_useImeFeedbackUsage() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
-            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+            VibrationAttributes attrs = provider.getVibrationAttributesForHapticFeedback(
                     effectId, /* flags */ 0,
                     HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
@@ -354,20 +478,34 @@
 
     @Test
     public void testIsRestricted_biometricConstants_returnsTrue() {
-        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+        HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
 
         for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
-            assertThat(hapticProvider.isRestrictedHapticFeedback(effectId)).isTrue();
+            assertThat(provider.isRestrictedHapticFeedback(effectId)).isTrue();
         }
     }
 
-    private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
-        return createProvider(/* customizations= */ null);
+    private HapticFeedbackVibrationProvider createProviderWithoutCustomizations() {
+        return createProvider(/* customizations= */ new SparseArray<>(),
+                /* customizationsRotary= */ new SparseArray<>(),
+                /* customizationsTouchScreen */ new SparseArray<>());
     }
 
     private HapticFeedbackVibrationProvider createProvider(
             SparseArray<VibrationEffect> customizations) {
-        return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
+        return createProvider(customizations, /* customizationsRotary= */ new SparseArray<>(),
+                /* customizationsTouchScreen */ new SparseArray<>());
+    }
+
+    private HapticFeedbackVibrationProvider createProvider(
+            @NonNull SparseArray<VibrationEffect> customizations,
+            @NonNull SparseArray<VibrationEffect> customizationsRotary,
+            @NonNull SparseArray<VibrationEffect> customizationsTouchScreen) {
+        return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo,
+                new HapticFeedbackCustomization(
+                        customizations,
+                        customizationsRotary,
+                        customizationsTouchScreen));
     }
 
     private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index f3ecfcc..66788b6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -45,6 +45,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -262,7 +264,7 @@
     public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
         assertFalse(mInputDeviceDelegate.isAvailable());
         assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
-                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                new CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
                 SYNCED_EFFECT));
     }
 
@@ -277,7 +279,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
         assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
-                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                new CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
                 SYNCED_EFFECT));
         verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
         verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
similarity index 80%
rename from services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
rename to services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
index 84f8412..f69d1c4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSessionTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -24,13 +24,13 @@
 
 import java.util.Arrays;
 
-public class VibrationTest {
+public class VibrationSessionTest {
 
     @Test
     public void status_hasUniqueProtoEnumValues() {
         assertThat(
-                Arrays.stream(Vibration.Status.values())
-                        .map(Vibration.Status::getProtoEnumValue)
+                Arrays.stream(VibrationSession.Status.values())
+                        .map(VibrationSession.Status::getProtoEnumValue)
                         .collect(toList()))
                 .containsNoDuplicates();
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 38cd49d..c7a136a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -81,6 +81,8 @@
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import org.junit.After;
 import org.junit.Before;
@@ -292,7 +294,7 @@
             if (expectedAllowedVibrations.contains(usage)) {
                 assertVibrationNotIgnoredForUsage(usage);
             } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_BACKGROUND);
             }
         }
     }
@@ -350,7 +352,7 @@
         createSystemReadyVibrationSettings();
 
         for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+            assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
         }
     }
 
@@ -365,7 +367,7 @@
         mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, wirelessChargingIntent);
 
         for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+            assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
         }
     }
 
@@ -377,7 +379,7 @@
         createSystemReadyVibrationSettings();
         // Check that initially, all usages are ignored due to the wireless charging.
         for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+            assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER);
         }
 
         Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB);
@@ -404,7 +406,7 @@
             if (expectedAllowedVibrations.contains(usage)) {
                 assertVibrationNotIgnoredForUsage(usage);
             } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_POWER);
             }
         }
     }
@@ -426,7 +428,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_RINGTONE || usage == USAGE_NOTIFICATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_RINGER_MODE);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -470,7 +472,7 @@
             if (usage == USAGE_ACCESSIBILITY) {
                 assertVibrationNotIgnoredForUsage(usage);
             } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             }
             assertVibrationNotIgnoredForUsageAndFlags(usage,
                     VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
@@ -512,7 +514,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_TOUCH) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -527,7 +529,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_TOUCH || usage == USAGE_IME_FEEDBACK) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -542,7 +544,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -557,7 +559,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_NOTIFICATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -574,7 +576,7 @@
 
         for (int usage : ALL_USAGES) {
             if (usage == USAGE_RINGTONE) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+                assertVibrationIgnoredForUsage(usage, Status.IGNORED_FOR_SETTINGS);
             } else {
                 assertVibrationNotIgnoredForUsage(usage);
             }
@@ -597,7 +599,7 @@
         mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
                 new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
 
-        assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+        assertVibrationIgnoredForUsage(USAGE_RINGTONE, Status.IGNORED_FOR_RINGER_MODE);
     }
 
     @Test
@@ -611,7 +613,7 @@
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
                         .build(),
-                Vibration.Status.IGNORED_FOR_SETTINGS);
+                Status.IGNORED_FOR_SETTINGS);
 
         // General touch and keyboard touch with bypass flag not ignored.
         assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
@@ -629,7 +631,7 @@
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
 
         // General touch ignored.
-        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Status.IGNORED_FOR_SETTINGS);
 
         // Keyboard touch not ignored.
         assertVibrationNotIgnoredForAttributes(
@@ -645,14 +647,14 @@
         setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
 
         // General touch ignored.
-        assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+        assertVibrationIgnoredForUsage(USAGE_TOUCH, Status.IGNORED_FOR_SETTINGS);
 
         // Keyboard touch ignored.
         assertVibrationIgnoredForAttributes(
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
                         .build(),
-                Vibration.Status.IGNORED_FOR_SETTINGS);
+                Status.IGNORED_FOR_SETTINGS);
     }
 
     @Test
@@ -668,7 +670,7 @@
         // Ignore the vibration when the coming device id represents a virtual device.
         for (int usage : ALL_USAGES) {
             assertVibrationIgnoredForUsageAndDevice(usage, VIRTUAL_DEVICE_ID,
-                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+                    Status.IGNORED_FROM_VIRTUAL_DEVICE);
         }
     }
 
@@ -911,22 +913,21 @@
     }
 
     private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
-            Vibration.Status expectedStatus) {
+            Status expectedStatus) {
         assertVibrationIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT, expectedStatus);
     }
 
     private void assertVibrationIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage,
-            int deviceId, Vibration.Status expectedStatus) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+            int deviceId, Status expectedStatus) {
+        CallerInfo callerInfo = new CallerInfo(
                 VibrationAttributes.createForUsage(usage), UID, deviceId, null, null);
         assertEquals(errorMessageForUsage(usage), expectedStatus,
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
     private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
-            Vibration.Status expectedStatus) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
-                Context.DEVICE_ID_DEFAULT, null, null);
+            Status expectedStatus) {
+        CallerInfo callerInfo = new CallerInfo(attrs, UID, Context.DEVICE_ID_DEFAULT, null, null);
         assertEquals(errorMessageForAttributes(attrs), expectedStatus,
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
@@ -948,7 +949,7 @@
     private void assertVibrationNotIgnoredForUsageAndFlagsAndDevice(
             @VibrationAttributes.Usage int usage, int deviceId,
             @VibrationAttributes.Flag int flags) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+        CallerInfo callerInfo = new CallerInfo(
                 new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
                 deviceId, null, null);
         assertNull(errorMessageForUsage(usage),
@@ -956,7 +957,7 @@
     }
 
     private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+        CallerInfo callerInfo = new CallerInfo(attrs, UID,
                 Context.DEVICE_ID_DEFAULT, null, null);
         assertNull(errorMessageForAttributes(attrs),
                 mVibrationSettings.shouldIgnoreVibration(callerInfo));
@@ -1017,10 +1018,10 @@
                 new PowerManager.SleepData(sleepTime, reason));
     }
 
-    private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
+    private CallerInfo createCallerInfo(int uid, String opPkg,
             @VibrationAttributes.Usage int usage) {
         VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
-        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null);
+        return new CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null);
     }
 
     private void setBatteryReceiverRegistrationResult(Intent result) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index bfdaa78..31cc50f1 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -78,6 +78,8 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
+import com.android.server.vibrator.VibrationSession.CallerInfo;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import org.junit.After;
 import org.junit.Before;
@@ -189,7 +191,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
     }
 
     @Test
@@ -202,7 +204,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
     }
 
     @Test
@@ -216,7 +218,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -233,7 +235,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -253,7 +255,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(15)),
@@ -326,13 +328,10 @@
         assertTrue(mThread.isRunningVibrationId(vibrationId));
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
-                Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
-                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
-                1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
-        mVibrationConductor.notifyCancelled(
-                cancelVibrationInfo,
-                /* immediate= */ false);
+        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
+                new CallerInfo(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM),
+                        /* uid= */ 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
+        mVibrationConductor.notifyCancelled(cancelVibrationInfo, /* immediate= */ false);
         waitForCompletion();
         assertFalse(mThread.isRunningVibrationId(vibrationId));
 
@@ -363,11 +362,10 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(5000)),
                 fakeVibrator.getEffectSegments(vibrationId));
@@ -385,7 +383,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
@@ -406,7 +404,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
@@ -433,11 +431,10 @@
         assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5,
                 5000L + TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5))
@@ -466,12 +463,11 @@
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         // PWLE size max was used to generate a single vibrate call with 10 segments.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
     }
@@ -496,12 +492,11 @@
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
         waitForCompletion();
 
         // Composition size max was used to generate a single vibrate call with 10 primitives.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
     }
@@ -519,11 +514,10 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(5550)),
                 fakeVibrator.getEffectSegments(vibrationId));
@@ -545,11 +539,10 @@
         assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
                 expectedOnDuration + TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibrationId);
         // First time, turn vibrator ON for the expected fixed duration.
@@ -584,14 +577,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -614,14 +607,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -641,14 +634,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -663,7 +656,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
@@ -684,7 +677,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -701,7 +694,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
     }
 
@@ -718,7 +711,7 @@
                 eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
@@ -743,7 +736,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
@@ -762,7 +755,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
     }
 
@@ -784,7 +777,7 @@
         long vibrationId = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         // Vibrator compose called twice.
         verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
@@ -824,7 +817,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedOneShot(10),
@@ -865,7 +858,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         List<VibrationEffectSegment> segments =
@@ -904,7 +897,7 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
@@ -942,7 +935,7 @@
         long vibrationId = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
         // Using best split points instead of max-packing PWLEs.
@@ -967,7 +960,7 @@
         waitForCompletion();
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
     }
 
     @Test
@@ -977,7 +970,7 @@
         long vibrationId = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100));
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
         verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
         verify(mManagerHooks, never()).cancelSyncedVibration();
@@ -998,7 +991,7 @@
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
@@ -1022,7 +1015,7 @@
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
         assertFalse(mControllers.get(3).isVibrating());
@@ -1065,7 +1058,7 @@
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
         assertFalse(mControllers.get(3).isVibrating());
@@ -1117,7 +1110,7 @@
         batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
         assertFalse(mControllers.get(3).isVibrating());
@@ -1166,7 +1159,7 @@
         verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
         verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
         verify(mManagerHooks, never()).cancelSyncedVibration();
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         VibrationEffectSegment expected = expectedPrimitive(
                 VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
@@ -1213,7 +1206,7 @@
         verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
         verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
         verify(mManagerHooks, never()).cancelSyncedVibration();
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
     }
 
     @Test
@@ -1305,7 +1298,7 @@
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
         assertFalse(mControllers.get(3).isVibrating());
@@ -1478,8 +1471,7 @@
         // fail at waitForCompletion(cancellingThread).
         Thread cancellingThread = new Thread(
                 () -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                        /* immediate= */ false));
+                        new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false));
         cancellingThread.start();
 
         // Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1488,7 +1480,7 @@
 
         // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
         waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -1517,14 +1509,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1551,14 +1543,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1585,15 +1577,14 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> mVibrationConductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF),
                         /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1612,7 +1603,7 @@
 
         verify(mVibrationToken).linkToDeath(same(mVibrationConductor), eq(0));
         verify(mVibrationToken).unlinkToDeath(same(mVibrationConductor), eq(0));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED);
         assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
@@ -1628,7 +1619,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         // Duration extended for 5 + 5 + 5 + 15.
         assertEquals(Arrays.asList(expectedOneShot(30)),
@@ -1651,7 +1642,7 @@
 
         // Vibration completed but vibrator not yet released.
         verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
-                eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
+                eq(new Vibration.EndInfo(Status.FINISHED)));
         verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
 
         // Thread still running ramp down.
@@ -1663,13 +1654,12 @@
 
         // Will stop the ramp down right away.
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
-                /* immediate= */ true);
+                new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE), /* immediate= */ true);
         waitForCompletion();
 
         // Does not cancel already finished vibration, but releases vibrator.
         verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
-                eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
+                eq(new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE)));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
@@ -1684,11 +1674,10 @@
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER);
 
         // Duration extended for 10000 + 15.
         assertEquals(Arrays.asList(expectedOneShot(10_015)),
@@ -1711,7 +1700,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
@@ -1729,7 +1718,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId))
                 .containsExactly(effect)
@@ -1752,7 +1741,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         assertEquals(
                 Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
@@ -1779,7 +1768,7 @@
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId, Status.FINISHED);
 
         assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
                 fakeVibrator.getEffectSegments(vibrationId));
@@ -1810,14 +1799,13 @@
         long vibrationId1 = startThreadAndDispatcher(effect1);
         waitForCompletion();
         verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
-        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
 
         long vibrationId2 = startThreadAndDispatcher(effect2);
         // Effect2 won't complete on its own. Cancel it after a couple of repeats.
         Thread.sleep(150);  // More than two TICKs.
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
+                new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         long vibrationId3 = startThreadAndDispatcher(effect3);
@@ -1827,8 +1815,7 @@
         long start4 = System.currentTimeMillis();
         long vibrationId4 = startThreadAndDispatcher(effect4);
         mVibrationConductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                /* immediate= */ true);
+                new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ true);
         waitForCompletion();
         long duration4 = System.currentTimeMillis() - start4;
 
@@ -1841,14 +1828,14 @@
 
         // Effect1
         verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
-        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId1, Status.FINISHED);
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 fakeVibrator.getEffectSegments(vibrationId1));
 
         // Effect2: repeating, cancelled.
         verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
-        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
+        verifyCallbacksTriggered(vibrationId2, Status.CANCELLED_BY_USER);
 
         // The exact count of segments might vary, so just check that there's more than 2 and
         // all elements are the same segment.
@@ -1860,13 +1847,13 @@
 
         // Effect3
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
-        verifyCallbacksTriggered(vibrationId3, Vibration.Status.FINISHED);
+        verifyCallbacksTriggered(vibrationId3, Status.FINISHED);
         assertEquals(Arrays.asList(
                         expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
                 fakeVibrator.getEffectSegments(vibrationId3));
 
         // Effect4: cancelled quickly.
-        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        verifyCallbacksTriggered(vibrationId4, Status.CANCELLED_BY_SCREEN_OFF);
         assertTrue("Tested duration=" + duration4, duration4 < 2000);
 
         // Effect5: played normally after effect4, which may or may not have played.
@@ -1907,7 +1894,7 @@
                 .build();
         HalVibration vib = new HalVibration(mVibrationToken,
                 CombinedVibration.createParallel(effect),
-                new Vibration.CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+                new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
         return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
     }
 
@@ -1944,7 +1931,7 @@
 
     private HalVibration createVibration(CombinedVibration effect) {
         return new HalVibration(mVibrationToken, effect,
-                new Vibration.CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
+                new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"));
     }
 
     private SparseArray<VibratorController> createVibratorControllers() {
@@ -2007,7 +1994,7 @@
                 .collect(Collectors.toList());
     }
 
-    private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
+    private void verifyCallbacksTriggered(long vibrationId, Status expectedStatus) {
         verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
     }
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
index 3466bbb..cd057b6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -22,6 +22,8 @@
 import android.os.Handler;
 import android.os.test.TestLooper;
 
+import com.android.server.vibrator.VibrationSession.Status;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -113,7 +115,6 @@
     }
 
     private static VibrationStats.StatsInfo newEmptyStatsInfo() {
-        return new VibrationStats.StatsInfo(
-                0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+        return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L);
     }
 }
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 4013587..4012575 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vibrator;
 
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -108,6 +110,7 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.vibrator.VibrationSession.Status;
 
 import org.junit.After;
 import org.junit.Before;
@@ -192,6 +195,11 @@
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private final SparseArray<VibrationEffect>  mHapticFeedbackVibrationMap = new SparseArray<>();
+    private final SparseArray<VibrationEffect>  mHapticFeedbackVibrationMapSourceRotary =
+            new SparseArray<>();
+    private final SparseArray<VibrationEffect>  mHapticFeedbackVibrationMapSourceTouchScreen =
+            new SparseArray<>();
+
     private final List<HalVibration> mPendingVibrations = new ArrayList<>();
 
     private VibratorManagerService mService;
@@ -339,8 +347,10 @@
                     @Override
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
                             Resources resources, VibratorInfo vibratorInfo) {
-                        return new HapticFeedbackVibrationProvider(
-                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
+                        return new HapticFeedbackVibrationProvider(resources, vibratorInfo,
+                                new HapticFeedbackCustomization(mHapticFeedbackVibrationMap,
+                                        mHapticFeedbackVibrationMapSourceRotary,
+                                        mHapticFeedbackVibrationMapSourceTouchScreen));
                     }
 
                     @Override
@@ -794,8 +804,8 @@
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
 
-        var vib = vibrate(service,
-                VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS);
+        HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+                new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), RINGTONE_ATTRS);
 
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
@@ -806,14 +816,80 @@
         service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
 
         assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+        assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_APP_OPS);
+    }
 
-        var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(statsInfoCaptor.capture());
+    @Test
+    public void vibrate_thenPowerModeChanges_getsCancelled() throws Exception {
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
 
-        VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
-        assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(),
-                touchMetrics.status);
+        HalVibration vib = vibrate(service,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+        assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE);
+    }
+
+    @Test
+    public void vibrate_thenSettingsRefreshedWithoutChange_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.updateServiceState();
+
+        // Vibration is not stopped nearly after updating service.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+    }
+
+    @Test
+    public void vibrate_thenSettingsChange_getsCancelled() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vib = vibrate(service,
+                VibrationEffect.createOneShot(2 * TEST_TIMEOUT_MILLIS, 100),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        service.mVibrationSettings.mSettingObserver.onChange(false);
+        service.updateServiceState();
+
+        assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+        assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE);
+    }
+
+    @Test
+    public void vibrate_thenScreenTurnsOff_getsCancelled() throws Throwable {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+                new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), ALARM_ATTRS);
+
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.mIntentReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_SCREEN_OFF));
+
+        assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+        assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_SCREEN_OFF);
     }
 
     @Test
@@ -822,8 +898,8 @@
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
 
-        var vib = vibrate(service,
-                VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), ALARM_ATTRS);
+        HalVibration vib = vibrate(service, VibrationEffect.createWaveform(
+                new long[]{0, TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, 0), ALARM_ATTRS);
 
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
@@ -832,14 +908,7 @@
                 BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
 
         assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
-
-        var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(statsInfoCaptor.capture());
-
-        VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
-        assertEquals(Vibration.Status.CANCELLED_BY_FOREGROUND_USER.getProtoEnumValue(),
-                touchMetrics.status);
+        assertThat(vib.getStatus()).isEqualTo(Status.CANCELLED_BY_FOREGROUND_USER);
     }
 
     @Test
@@ -1305,7 +1374,7 @@
     }
 
     @Test
-    public void vibrate_withriggerCallback_finishesVibration() throws Exception {
+    public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
         mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1475,6 +1544,60 @@
     }
 
     @Test
+    public void performHapticFeedbackForInputDevice_doesNotRequireVibrateOrBypassPermissions()
+            throws Exception {
+        // Deny permissions that would have been required for regular vibrations, and check that
+        // the vibration proceed as expected to verify that haptic feedback does not need these
+        // permissions.
+        denyPermission(android.Manifest.permission.VIBRATE);
+        denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+        // Flag override to enable the scroll feedback constants to bypass interruption policies.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        mHapticFeedbackVibrationMapSourceRotary.put(
+                HapticFeedbackConstants.SCROLL_TICK,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mHapticFeedbackVibrationMapSourceTouchScreen.put(
+                HapticFeedbackConstants.DRAG_START,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibrationByRotary =
+                performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+                        InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+        HalVibration vibrationByTouchScreen =
+                performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.DRAG_START, /* inputDeviceId= */ 0,
+                        InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
+
+        List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+        // 2 haptics: 1 by rotary + 1 by touch screen
+        assertEquals(2, playedSegments.size());
+        // Verify feedback by rotary input
+        PrebakedSegment segmentByRotary = (PrebakedSegment) playedSegments.get(0);
+        assertEquals(VibrationEffect.EFFECT_CLICK, segmentByRotary.getEffectId());
+        VibrationAttributes attrsByRotary = vibrationByRotary.callerInfo.attrs;
+        assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrsByRotary.getUsage());
+        assertTrue(attrsByRotary.isFlagSet(
+                VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+        assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+        // Verify feedback by touch screen input
+        PrebakedSegment segmentByTouchScreen = (PrebakedSegment) playedSegments.get(1);
+        assertEquals(VibrationEffect.EFFECT_TICK, segmentByTouchScreen.getEffectId());
+        VibrationAttributes attrsByTouchScreen = vibrationByTouchScreen.callerInfo.attrs;
+        assertEquals(VibrationAttributes.USAGE_TOUCH, attrsByTouchScreen.getUsage());
+        assertTrue(attrsByRotary.isFlagSet(
+                VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+        assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+    }
+
+    @Test
     public void performHapticFeedback_restrictedConstantsWithoutPermission_doesNotVibrate()
             throws Exception {
         // Deny permission to vibrate with restricted constants
@@ -1506,6 +1629,42 @@
     }
 
     @Test
+    public void performHapticFeedbackForInputDevice_restrictedConstantsWithoutPermission_doesNotVibrate()
+            throws Exception {
+        // Deny permission to vibrate with restricted constants
+        denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        // Public constant, no permission required
+        mHapticFeedbackVibrationMapSourceRotary.put(
+                HapticFeedbackConstants.CONFIRM,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        // Hidden system-only constant, permission required
+        mHapticFeedbackVibrationMapSourceTouchScreen.put(
+                HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(
+                VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        // This vibrates.
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+        // This doesn't.
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+        List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+        assertEquals(1, playedSegments.size());
+        PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+        assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+    }
+
+    @Test
     public void performHapticFeedback_restrictedConstantsWithPermission_playsVibration()
             throws Exception {
         // Grant permission to vibrate with restricted constants
@@ -1539,33 +1698,95 @@
     }
 
     @Test
+    public void performHapticFeedbackForInputDevice_restrictedConstantsWithPermission_playsVibration()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+        // Grant permission to vibrate with restricted constants
+        grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS);
+        // Public constant, no permission required
+        mHapticFeedbackVibrationMapSourceRotary.put(
+                HapticFeedbackConstants.CONFIRM,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        // Hidden system-only constant, permission required
+        mHapticFeedbackVibrationMapSourceTouchScreen.put(
+                HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(
+                VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, HapticFeedbackConstants.CONFIRM, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ false);
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_TOUCHSCREEN, /* always= */ false);
+
+        List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+        assertEquals(2, playedSegments.size());
+        assertEquals(VibrationEffect.EFFECT_CLICK,
+                ((PrebakedSegment) playedSegments.get(0)).getEffectId());
+        assertEquals(VibrationEffect.EFFECT_HEAVY_CLICK,
+                ((PrebakedSegment) playedSegments.get(1)).getEffectId());
+    }
+
+    @Test
     public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
         denyPermission(android.Manifest.permission.VIBRATE);
         mHapticFeedbackVibrationMap.put(
                 HapticFeedbackConstants.KEYBOARD_TAP,
                 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mHapticFeedbackVibrationMapSourceRotary.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD));
+        mHapticFeedbackVibrationMapSourceTouchScreen.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setVibratorInfoLoadSuccessful(false);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK,
+                VibrationEffect.EFFECT_THUD);
         VibratorManagerService service = createService();
 
+        // performHapticFeedback.
         performHapticFeedbackAndWaitUntilFinished(
                 service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+        // performHapticFeedbackForInputDevice.
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, HapticFeedbackConstants.KEYBOARD_TAP, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
 
         assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
     }
 
     @Test
     public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
+        mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
         denyPermission(android.Manifest.permission.VIBRATE);
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
 
         // These are bad haptic feedback IDs, so expect no vibration played.
+        // Test performHapticFeedback
         performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
         performHapticFeedbackAndWaitUntilFinished(
                 service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+        // Test performHapticFeedbackForInputDevice
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, /* constant= */ -1, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+        performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, /* constant= */ -1, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true);
 
         assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
     }
@@ -1582,6 +1803,17 @@
     }
 
     @Test
+    public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception {
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+                service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true);
+
+        assertTrue(vibration.callerToken == service);
+    }
+
+    @Test
     @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception {
         // Deny permission to vibrate with vendor effects
@@ -1775,41 +2007,6 @@
     }
 
     @Test
-    public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
-        mockVibrators(1, 2);
-        VibratorManagerService service = createSystemReadyService();
-        vibrate(service,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-
-        // Haptic feedback cancelled on low power mode.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        service.updateServiceState();
-
-        // Vibration is not stopped nearly after updating service.
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
-    }
-
-    @Test
     public void vibrate_ignoreVibrationFromVirtualDevice() throws Exception {
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
@@ -2303,6 +2500,113 @@
     }
 
     @Test
+    public void onExternalVibration_thenDeniedAppOps_doNotCancelVibration() throws Throwable {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        IExternalVibrationController externalVibrationControllerMock =
+                mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+        when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+        service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
+
+        verify(externalVibrationControllerMock, never()).mute();
+    }
+
+    @Test
+    public void onExternalVibration_thenPowerModeChanges_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IExternalVibrationController externalVibrationControllerMock =
+                mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        verify(externalVibrationControllerMock, never()).mute();
+    }
+
+    @Test
+    public void onExternalVibration_thenSettingsChange_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        IExternalVibrationController externalVibrationControllerMock =
+                mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        service.mVibrationSettings.mSettingObserver.onChange(false);
+        service.updateServiceState();
+
+        verify(externalVibrationControllerMock, never()).mute();
+    }
+
+    @Test
+    public void onExternalVibration_thenScreenTurnsOff_doNotCancelVibration() throws Throwable {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        IExternalVibrationController externalVibrationControllerMock =
+                mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+        service.mIntentReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_SCREEN_OFF));
+
+        verify(externalVibrationControllerMock, never()).mute();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
+    public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        IExternalVibrationController externalVibrationControllerMock =
+                mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, externalVibrationControllerMock, mock(IBinder.class));
+        ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+                externalVibration);
+
+        assertThat(scale.scaleLevel).isNotEqualTo(ExternalVibrationScale.ScaleLevel.SCALE_MUTE);
+
+        service.mIntentReceiver.onReceive(mContextSpy, new Intent(
+                BackgroundUserSoundNotifier.ACTION_MUTE_SOUND));
+
+        verify(externalVibrationControllerMock, never()).mute();
+    }
+
+    @Test
     public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -2329,7 +2633,7 @@
         assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
                 statsInfo.vibrationType);
         assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+        assertEquals(Status.FINISHED.getProtoEnumValue(), statsInfo.status);
         assertTrue(statsInfo.totalDurationMillis > 0);
         assertTrue(
                 "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
@@ -2361,7 +2665,7 @@
         assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
                 metrics.vibrationType);
         assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
         assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
                 metrics.totalDurationMillis >= 20);
         assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
@@ -2414,7 +2718,7 @@
         assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
                 metrics.vibrationType);
         assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
-        assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+        assertEquals(Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
         assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
                 metrics.totalDurationMillis >= 100);
         assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
@@ -2475,7 +2779,7 @@
         assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
                 metrics.vibrationType);
         assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
 
         // At least 4 effect/primitive played, 20ms each, plus configured fallback.
         assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
@@ -2531,7 +2835,7 @@
         VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
         assertEquals(UID, touchMetrics.uid);
         assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
-        assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+        assertEquals(Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
                 touchMetrics.status);
         assertTrue(touchMetrics.endedBySameUid);
         assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
@@ -2540,7 +2844,7 @@
         VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
         assertEquals(UID, alarmMetrics.uid);
         assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+        assertEquals(Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
         assertFalse(alarmMetrics.endedBySameUid);
         assertEquals(-1, alarmMetrics.endedByUsage);
         assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
@@ -2570,12 +2874,12 @@
         VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
         assertEquals(UID, touchMetrics.uid);
         assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
-        assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+        assertEquals(Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
 
         VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
         assertEquals(UID, ringtoneMetrics.uid);
         assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+        assertEquals(Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
                 ringtoneMetrics.status);
 
         for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
@@ -2642,7 +2946,7 @@
         assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
                 metrics.vibrationType);
         assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertEquals(Status.FINISHED.getProtoEnumValue(), metrics.status);
         assertTrue(metrics.totalDurationMillis >= 20);
 
         // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
@@ -2761,6 +3065,21 @@
         return vib;
     }
 
+    private HalVibration performHapticFeedbackForInputDeviceAndWaitUntilFinished(
+            VibratorManagerService service, int constant, int inputDeviceId, int inputSource,
+            boolean always) throws InterruptedException {
+        HalVibration vib = service.performHapticFeedbackForInputDeviceInternal(UID,
+                Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, constant, inputDeviceId, inputSource,
+                "some reason", service,
+                always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+                0 /* privFlags */);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+
+        return vib;
+    }
+
     private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
             VibrationEffect effect, VibrationAttributes attrs) throws InterruptedException {
         return vibrateAndWaitUntilFinished(
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 0d8b720..1e035da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1723,10 +1723,12 @@
     @Test
     public void testDestroyImmediately_hadApp_notFinishing() {
         final ActivityRecord activity = createActivityWithTask();
+        activity.idle = true;
         activity.finishing = false;
         activity.destroyImmediately("test");
 
         assertEquals(DESTROYED, activity.getState());
+        assertFalse(activity.idle);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 1e8c3b2..6f7d0dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -36,7 +36,6 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
@@ -728,51 +727,6 @@
         }
     }
 
-    @android.platform.test.annotations.RequiresFlagsDisabled(
-            com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
-    @SetupWindows(addWindows = W_INPUT_METHOD)
-    @Test
-    public void testLaunchRemoteAnimationWithoutImeBehind() {
-        final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
-        final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
-
-        // Simulating win1 has shown IME and being IME layering/input target
-        mDisplayContent.setImeLayeringTarget(win1);
-        mDisplayContent.setImeInputTarget(win1);
-        mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
-        spyOn(mDisplayContent);
-        mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
-        makeWindowVisibleAndDrawn(mImeWindow);
-        assertTrue(mImeWindow.isOnScreen());
-        assertFalse(mImeWindow.isParentWindowHidden());
-
-        try {
-            // Simulating now win1 is being covered by the lockscreen which has no surface,
-            // and then launching an activity win2 with the remote animation
-            win1.mHasSurface = false;
-            win1.mActivityRecord.setVisibility(false);
-            mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
-            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
-                    win2.mActivityRecord, new Point(50, 100), null,
-                    new Rect(50, 100, 150, 150), null, false).mAdapter;
-            adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
-                    mFinishedCallback);
-
-            mDisplayContent.applySurfaceChangesTransaction();
-            mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
-            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-
-            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
-                    any(), any(), any(), any());
-            // Verify the IME window won't apply surface change transaction with forAllImeWindows
-            verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true));
-        } catch (Exception e) {
-            // no-op
-        } finally {
-            mDisplayContent.mOpeningApps.clear();
-        }
-    }
-
     private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mDisplayContent.mOpeningApps.add(win.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 6adf0fe..7cb62c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1420,20 +1420,4 @@
         verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
                 anyBoolean());
     }
-
-    private void verifyShouldSleepActivities(boolean focusedRootTask,
-            boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
-            boolean expected) {
-        final Task task = new TaskBuilder(mSupervisor).build();
-        final DisplayContent display = mock(DisplayContent.class);
-        final KeyguardController keyguardController = mSupervisor.getKeyguardController();
-        display.isDefaultDisplay = isDefaultDisplay;
-
-        task.mDisplayContent = display;
-        doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
-        doReturn(displaySleeping).when(display).isSleeping();
-        doReturn(focusedRootTask).when(task).isFocusedRootTaskOnDisplay();
-
-        assertEquals(expected, task.shouldSleepActivities());
-    }
 }
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 f1db713..957b5e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -371,8 +371,7 @@
         ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
 
         // Move first activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
-                null /* launchIntoPipHostActivity */, "initialMove");
+        mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove");
 
         final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
         Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -381,8 +380,7 @@
         ensureTaskPlacement(fullscreenTask, secondActivity);
 
         // Move second activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
-                null /* launchIntoPipHostActivity */, "secondMove");
+        mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove");
 
         // Need to get root tasks again as a new instance might have been created.
         pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -413,8 +411,7 @@
 
 
         // Move first activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
-                null /* launchIntoPipHostActivity */, "initialMove");
+        mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
 
         assertTrue(firstActivity.mRequestForceTransition);
     }
@@ -434,8 +431,7 @@
         transientActivity.setState(RESUMED, "test");
         transientActivity.getTask().moveToFront("test");
 
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity2,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity2, "test");
         assertEquals("Created PiP task must not change focus", transientActivity.getTask(),
                 mRootWindowContainer.getTopDisplayFocusedRootTask());
         final Task newPipTask = activity2.getTask();
@@ -460,8 +456,7 @@
         final Task task = activity.getTask();
 
         // Move activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
 
         // Ensure a task has moved over.
         ensureTaskPlacement(task, activity);
@@ -499,8 +494,7 @@
         final Task task = activity.getTask();
 
         // Move activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
 
         // Ensure a task has moved over.
         ensureTaskPlacement(task, activity);
@@ -524,8 +518,7 @@
         final ActivityRecord secondActivity = taskFragment.getBottomMostActivity();
 
         // Move first activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "test");
 
         final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
         final Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -556,8 +549,7 @@
         final ActivityRecord topActivity = taskFragment.getTopMostActivity();
 
         // Move the top activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(topActivity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(topActivity, "test");
 
         final Task pinnedRootTask = task.getDisplayArea().getRootPinnedTask();
 
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 6be1af2..cc1805a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -368,8 +368,7 @@
         assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
 
         // Move activity to pinned root task.
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
 
         // Ensure taskFragment requested config is reset.
         assertEquals(taskFragment0, activity.getOrganizedTaskFragment());
@@ -399,8 +398,7 @@
         spyOn(mAtm.mTaskFragmentOrganizerController);
 
         // Move activity to pinned.
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity0,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity0, "test");
 
         // Ensure taskFragment requested config is reset.
         assertTrue(taskFragment0.mClearedTaskFragmentForPip);
@@ -434,8 +432,7 @@
                 .createActivityCount(1)
                 .build();
         final ActivityRecord activity = taskFragment.getTopMostActivity();
-        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
-                null /* launchIntoPipHostActivity */, "test");
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity, "test");
         spyOn(mAtm.mTaskFragmentOrganizerController);
         assertEquals(mIOrganizer, activity.mLastTaskFragmentOrganizerBeforePip);
 
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 55f74e9d..45082d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -230,8 +230,7 @@
         final Task originalTask = activityMain.getTask();
         final ActivityRecord activityPip = new ActivityBuilder(mAtm).setTask(originalTask).build();
         activityPip.setState(RESUMED, "test");
-        mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip,
-                null /* launchIntoPipHostActivity */, "test");
+        mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip, "test");
         final Task pinnedActivityTask = activityPip.getTask();
 
         // Simulate pinnedActivityTask unintentionally added to recent during top activity resume.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 56fca31..7320c0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1251,7 +1251,7 @@
         final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
         app.mTransitionController.requestStartTransition(transition, app.getTask(),
                 null /* remoteTransition */, null /* displayChange */);
-        app.mTransitionController.collectExistenceChange(app.getTask());
+        transition.collectExistenceChange(app.getTask());
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
@@ -1416,7 +1416,8 @@
         activity1.setVisibleRequested(false);
         activity2.setVisibleRequested(true);
 
-        openTransition.finishTransition();
+        final ActionChain chain = ActionChain.testFinish(null);
+        openTransition.finishTransition(chain);
 
         // We finished the openTransition. Even though activity1 is visibleRequested=false, since
         // the closeTransition animation hasn't played yet, make sure that we didn't commit
@@ -1429,7 +1430,7 @@
         // normally.
         mWm.mSyncEngine.abort(closeTransition.getSyncId());
 
-        closeTransition.finishTransition();
+        closeTransition.finishTransition(chain);
 
         assertFalse(activity1.isVisible());
         assertTrue(activity2.isVisible());
@@ -1449,7 +1450,7 @@
         activity1.setState(ActivityRecord.State.INITIALIZING, "test");
         activity1.mLaunchTaskBehind = true;
         mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
-        noChangeTransition.finishTransition();
+        noChangeTransition.finishTransition(chain);
         assertTrue(activity1.mLaunchTaskBehind);
     }
 
@@ -1468,7 +1469,7 @@
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
         // normally.
         mWm.mSyncEngine.abort(transition1.getSyncId());
-        transition1.finishTransition();
+        transition1.finishTransition(ActionChain.testFinish(transition1));
 
         verify(transitionEndedListener).run();
 
@@ -1530,7 +1531,7 @@
 
         verify(taskSnapshotController, times(1)).recordSnapshot(eq(task2));
 
-        controller.finishTransition(openTransition);
+        controller.finishTransition(ActionChain.testFinish(openTransition));
 
         // We are now going to simulate closing task1 to return back to (open) task2.
         final Transition closeTransition = createTestTransition(TRANSIT_CLOSE, controller);
@@ -1595,7 +1596,7 @@
         doReturn(true).when(task1).isTranslucentForTransition();
         assertFalse(controller.canApplyDim(task1));
 
-        controller.finishTransition(closeTransition);
+        controller.finishTransition(ActionChain.testFinish(closeTransition));
         assertTrue(wasInFinishingTransition[0]);
         assertFalse(calledListenerOnOtherDisplay[0]);
         assertNull(controller.mFinishingTransition);
@@ -1651,7 +1652,7 @@
         // to avoid the latency to resume the current top, i.e. appB.
         assertTrue(controller.isTransientVisible(taskRecent));
         // The recent is paused after the transient transition is finished.
-        controller.finishTransition(transition);
+        controller.finishTransition(ActionChain.testFinish(transition));
         assertFalse(controller.isTransientVisible(taskRecent));
     }
 
@@ -2004,10 +2005,10 @@
     @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCommonAnimOptions("testPackage");
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2018,10 +2019,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCommonAnimOptions("testPackage");
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2044,10 +2045,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeSceneTransitionAnimOptions();
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2070,10 +2071,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCrossProfileAnimOptions();
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2098,13 +2099,13 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, false /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2131,7 +2132,7 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
 
         final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
         embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
@@ -2144,7 +2145,7 @@
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, false /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2180,13 +2181,13 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
-        initializeOverrideAnimationOptionsTest();
+        ActivityRecord r = initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, true /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2212,7 +2213,7 @@
                 options.getBackgroundColor(), activityChange.getBackgroundColor());
     }
 
-    private void initializeOverrideAnimationOptionsTest() {
+    private ActivityRecord initializeOverrideAnimationOptionsTest() {
         mTransition = createTestTransition(TRANSIT_OPEN);
 
         // Test set AnimationOptions for Activity and Task.
@@ -2240,6 +2241,7 @@
                 embeddedTf.getAnimationLeash()));
         mInfo.addChange(new TransitionInfo.Change(null /* container */,
                 nonEmbeddedActivity.getAnimationLeash()));
+        return nonEmbeddedActivity;
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 7652861..9602ae2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -407,7 +407,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, token.getSyncGroup(), false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit);
+        dc.mTransitionController.finishTransition(ActionChain.testFinish(transit));
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 22def51..72935cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -62,8 +62,6 @@
         verify(c).accept(eq(mImeWindow));
     }
 
-    @android.platform.test.annotations.RequiresFlagsEnabled(
-            com.android.window.flags.Flags.FLAG_DO_NOT_SKIP_IME_BY_TARGET_VISIBILITY)
     @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testTraverseImeRegardlessOfImeTarget() {
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 39276a1..5a54af1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -310,10 +310,8 @@
         // Simulate the window is in split screen root task.
         final Task rootTask = createTask(mDisplayContent,
                 WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
-        spyOn(appWindow);
-        spyOn(rootTask);
         rootTask.setFocusable(false);
-        doReturn(rootTask).when(appWindow).getRootTask();
+        appWindow.mActivityRecord.reparent(rootTask, 0 /* position */, "test");
 
         // Make sure canBeImeTarget is false;
         assertFalse(appWindow.canBeImeTarget());
@@ -1035,7 +1033,7 @@
                 mDisplayContent,
                 "SystemDialog", true);
         mDisplayContent.setImeLayeringTarget(mAppWindow);
-        mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        mAppWindow.getTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         makeWindowVisible(mImeWindow);
         systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
         assertTrue(systemDialogWindow.needsRelativeLayeringToIme());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index bcf4ebc..a215c0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -2131,7 +2131,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit);
+            mController.finishTransition(ActionChain.testFinish(mLastTransit));
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
index 1d567b1..c45b99d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
@@ -39,8 +39,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Mockito;
 
 import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency;
 
@@ -50,9 +49,7 @@
 @SmallTest
 @Presubmit
 public class WindowTracingPerfettoTest {
-    @Mock
     private WindowManagerService mWmMock;
-    @Mock
     private Choreographer mChoreographer;
     private WindowTracing mWindowTracing;
     private PerfettoTraceMonitor mTraceMonitor;
@@ -60,7 +57,10 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        mWmMock = Mockito.mock(WindowManagerService.class);
+        Mockito.doNothing().when(mWmMock).dumpDebugLocked(Mockito.any(), Mockito.anyInt());
+
+        mChoreographer = Mockito.mock(Choreographer.class);
 
         mWindowTracing = new WindowTracingPerfetto(mWmMock, mChoreographer,
                 new WindowManagerGlobalLock());
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
index 381e9e4..46b8e3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java
@@ -234,11 +234,11 @@
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
-    public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsFalse() {
+    public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_OFF.getSetting());
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
+        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
     }
 
     @Test
@@ -296,11 +296,11 @@
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
             FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
     })
-    public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnTrue() {
+    public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnFalse() {
         setOverride(OVERRIDE_ON.getSetting());
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isTrue();
+        assertThat(DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)).isFalse();
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 808a575..1e1abf2 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -39,7 +39,10 @@
 
 /**
  * CallControl provides client side control of a call.  Each Call will get an individual CallControl
- * instance in which the client can alter the state of the associated call.
+ * instance in which the client can alter the state of the associated call.  Outgoing and incoming
+ * calls should move to active (via {@link CallControl#setActive(Executor, OutcomeReceiver)} or
+ * answered (via {@link CallControl#answer(int, Executor, OutcomeReceiver)} before 60 seconds.  If
+ * the new call is not moved to active or answered before 60 seconds, the call will be disconnected.
  *
  * <p>
  * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 1df6cf7..ad7d987 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2621,7 +2621,9 @@
     }
 
     /**
-     * Sets state to ringing (e.g., an inbound ringing connection).
+     * Sets state to ringing (e.g., an inbound ringing connection).  The Connection should not be
+     * in STATE_RINGING for more than 60 seconds. After 60 seconds, the Connection will
+     * be disconnected.
      */
     public final void setRinging() {
         checkImmutable();
@@ -2645,7 +2647,9 @@
     }
 
     /**
-     * Sets state to dialing (e.g., dialing an outbound connection).
+     * Sets state to dialing (e.g., dialing an outbound connection). The Connection should not be
+     * in STATE_DIALING for more than 60 seconds. After 60 seconds, the Connection will
+     * be disconnected.
      */
     public final void setDialing() {
         checkImmutable();
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index e6fe406..83dac18 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -175,8 +175,15 @@
      * <p>
      * The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is
      * in progress.
+     *
+     * @deprecated this API was only intended to prevent call recording via the microphone by an app
+     * while in a phone call.  Audio policies no longer make this possible.  Further, this API was
+     * never actually used.  Call recording solutions integrated in an OEM dialer app must use
+     * appropriate recording signals to inform the caller/callee of the recording.
      * @hide
      */
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    @Deprecated
     @SystemApi
     public static final String EXTRA_PLAY_CALL_RECORDING_TONE =
             "android.telecom.extra.PLAY_CALL_RECORDING_TONE";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index afd5720..cba2eea 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -420,6 +420,7 @@
      * <p>
      * Note: This requires the Telephony config_supports_telephony_audio_device overlay to be true
      * in order to work.
+     * @deprecated this functionality was never used and is no longer supported.
      * @hide
      */
     public static final String KEY_PLAY_CALL_RECORDING_TONE_BOOL = "play_call_recording_tone_bool";
@@ -9993,6 +9994,17 @@
     public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
 
     /**
+     * Indicate whether carrier roaming to satellite is using P2P SMS.
+     *
+     * This will need agreement with carriers before enabling this flag.
+     *
+     * The default value is false.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL =
+            "satellite_roaming_p2p_sms_supported_bool";
+
+    /**
      * Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite
      * attachment. For more on NIDD, see 3GPP TS 29.542.
      * Note this config is the only source of truth regarding the definition of the APN.
@@ -10074,7 +10086,35 @@
      */
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final String KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT =
-            "satellite_screen_off_inactivity_timeout_duration_sec_int";
+            "satellite_screen_off_inactivity_timeout_sec_int";
+
+    /**
+     * An integer key holds the timeout duration in seconds used to determine whether to exit P2P
+     * SMS mode and start TN scanning.
+     *
+     * The timer is started when the device is not connected, user is not pointing to the
+     * satellite and no data transfer is happening.
+     * When the timer expires, the device will move to IDLE mode upon which TN scanning will start.
+     *
+     * The default value is 180 seconds.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final String KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT =
+            "satellite_p2p_sms_inactivity_timeout_sec_int";
+
+    /**
+     * An integer key holds the timeout duration in seconds used to determine whether to exit ESOS
+     * mode and start TN scanning.
+     *
+     * The timer is started when the device is not connected, user is not pointing to the
+     * satellite and no data transfer is happening.
+     * When the timer expires, the device will move to IDLE mode upon which TN scanning will start.
+     *
+     * The default value is 600 seconds.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final String KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT =
+            "satellite_esos_inactivity_timeout_sec_int";
 
     /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11234,12 +11274,15 @@
         sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
                 (int) TimeUnit.SECONDS.toMillis(30));
         sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
+        sDefaults.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, false);
         sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
         sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
         sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
                 SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
         sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
         sDefaults.putInt(KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
+        sDefaults.putInt(KEY_SATELLITE_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT, 180);
+        sDefaults.putInt(KEY_SATELLITE_ESOS_INACTIVITY_TIMEOUT_SEC_INT, 600);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 6ef953c..c5934a7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -546,6 +546,16 @@
     public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2;
 
     /**
+     * This intent will be broadcasted if there are any change to list of subscriber informations.
+     * This intent will be sent only to the app with component defined in
+     * config_satellite_carrier_roaming_esos_provisioned_class and package defined in
+     * config_satellite_gateway_service_package
+     * @hide
+     */
+    public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED =
+            "android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
+
+    /**
      * Request to enable or disable the satellite modem and demo mode.
      * If satellite modem and cellular modem cannot work concurrently,
      * then this will disable the cellular modem if satellite modem is enabled,
diff --git a/tests/Input/assets/testPointerFillStyle.png b/tests/Input/assets/testPointerFillStyle.png
index b2354f8..297244f 100644
--- a/tests/Input/assets/testPointerFillStyle.png
+++ b/tests/Input/assets/testPointerFillStyle.png
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 05a68e9..6db5f82 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -798,6 +798,38 @@
                 .isEqualTo("My Test Debug Log Message true");
     }
 
+    @Test
+    public void usesDefaultLogFromLevel() throws IOException {
+        PerfettoTraceMonitor traceMonitor =
+                PerfettoTraceMonitor.newBuilder().enableProtoLog(LogLevel.WARN).build();
+        try {
+            traceMonitor.start();
+            mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+                "This message should not be logged");
+            mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+                "This message should logged %d", 123);
+            mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+                "This message should also be logged %d", 567);
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+        Truth.assertThat(protolog.messages).hasSize(2);
+
+        Truth.assertThat(protolog.messages.get(0).getLevel())
+                .isEqualTo(LogLevel.WARN);
+        Truth.assertThat(protolog.messages.get(0).getMessage())
+                .isEqualTo("This message should logged 123");
+
+        Truth.assertThat(protolog.messages.get(1).getLevel())
+                .isEqualTo(LogLevel.ERROR);
+        Truth.assertThat(protolog.messages.get(1).getMessage())
+                .isEqualTo("This message should also be logged 567");
+    }
+
     private enum TestProtoLogGroup implements IProtoLogGroup {
         TEST_GROUP(true, true, false, "TEST_TAG");
 
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
index e3ec62d..aba6722 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -41,14 +41,14 @@
 public class ProtoLogCommandHandlerTest {
 
     @Mock
-    ProtoLogService mProtoLogService;
+    ProtoLogConfigurationService mProtoLogConfigurationService;
     @Mock
     PrintWriter mPrintWriter;
 
     @Test
     public void printsHelpForAllAvailableCommands() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
         cmdHandler.onHelp();
         validateOnHelpPrinted();
@@ -57,7 +57,7 @@
     @Test
     public void printsHelpIfCommandIsNull() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
         cmdHandler.onCommand(null);
         validateOnHelpPrinted();
@@ -65,13 +65,13 @@
 
     @Test
     public void handlesGroupListCommand() {
-        Mockito.when(mProtoLogService.getGroups())
+        Mockito.when(mProtoLogConfigurationService.getGroups())
                 .thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "groups", "list" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "groups", "list" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("MY_TEST_GROUP"));
@@ -82,10 +82,10 @@
     @Test
     public void handlesIncompleteGroupsCommand() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "groups" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "groups" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("Incomplete command"));
@@ -93,13 +93,14 @@
 
     @Test
     public void handlesGroupStatusCommand() {
-        Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {"MY_GROUP"});
-        Mockito.when(mProtoLogService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+        Mockito.when(mProtoLogConfigurationService.getGroups())
+                .thenReturn(new String[] {"MY_GROUP"});
+        Mockito.when(mProtoLogConfigurationService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "groups", "status", "MY_GROUP" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("MY_GROUP"));
@@ -109,12 +110,12 @@
 
     @Test
     public void handlesGroupStatusCommandOfUnregisteredGroups() {
-        Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {});
+        Mockito.when(mProtoLogConfigurationService.getGroups()).thenReturn(new String[] {});
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "groups", "status", "MY_GROUP" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("MY_GROUP"));
@@ -125,10 +126,10 @@
     @Test
     public void handlesGroupStatusCommandWithNoGroups() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "groups", "status" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "groups", "status" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("Incomplete command"));
@@ -137,10 +138,10 @@
     @Test
     public void handlesIncompleteLogcatCommand() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "logcat" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "logcat" });
 
         Mockito.verify(mPrintWriter, times(1))
                 .println(contains("Incomplete command"));
@@ -149,50 +150,52 @@
     @Test
     public void handlesLogcatEnableCommand() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "logcat", "enable", "MY_GROUP" });
-        Mockito.verify(mProtoLogService).enableProtoLogToLogcat("MY_GROUP");
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
+        Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err,
                 new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
-        Mockito.verify(mProtoLogService)
+        Mockito.verify(mProtoLogConfigurationService)
                 .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
     }
 
     @Test
     public void handlesLogcatDisableCommand() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "logcat", "disable", "MY_GROUP" });
-        Mockito.verify(mProtoLogService).disableProtoLogToLogcat("MY_GROUP");
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
+        Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err,
                 new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
-        Mockito.verify(mProtoLogService)
+        Mockito.verify(mProtoLogConfigurationService)
                 .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
     }
 
     @Test
     public void handlesLogcatEnableCommandWithNoGroups() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "logcat", "enable" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "logcat", "enable" });
         Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
     }
 
     @Test
     public void handlesLogcatDisableCommandWithNoGroups() {
         final ProtoLogCommandHandler cmdHandler =
-                new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+                new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
 
-        cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                new String[] { "logcat", "disable" });
+        cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out,
+                FileDescriptor.err, new String[] { "logcat", "disable" });
         Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
     }
 
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
similarity index 76%
rename from tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
index feac59c..e1bdd77 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -67,7 +67,7 @@
  */
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
-public class ProtoLogServiceTest {
+public class ProtoLogConfigurationServiceTest {
 
     private static final String TEST_GROUP = "MY_TEST_GROUP";
     private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
@@ -128,7 +128,7 @@
 
     private File mViewerConfigFile;
 
-    public ProtoLogServiceTest() throws IOException {
+    public ProtoLogConfigurationServiceTest() throws IOException {
     }
 
     @Before
@@ -150,10 +150,12 @@
 
     @Test
     public void canRegisterClientWithGroupsOnly() throws RemoteException {
-        final ProtoLogService service = new ProtoLogService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, true));
         service.registerClient(mMockClient, args);
 
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -163,11 +165,13 @@
     @Test
     public void willDumpViewerConfigOnlyOnceOnTraceStop()
             throws RemoteException, InvalidProtocolBufferException {
-        final ProtoLogService service = new ProtoLogService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true))
-                .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, true))
+                        .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
         service.registerClient(mMockClient, args);
         service.registerClient(mSecondMockClient, args);
 
@@ -196,14 +200,15 @@
     @Test
     public void willDumpViewerConfigOnLastClientDisconnected()
             throws RemoteException, FileNotFoundException {
-        final ProtoLogService.ViewerConfigFileTracer tracer =
-                Mockito.mock(ProtoLogService.ViewerConfigFileTracer.class);
-        final ProtoLogService service = new ProtoLogService(tracer);
+        final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
+                Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(
-                        TEST_GROUP, true))
-                .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, true))
+                        .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
         service.registerClient(mMockClient, args);
         service.registerClient(mSecondMockClient, args);
 
@@ -220,10 +225,11 @@
 
     @Test
     public void sendEnableLoggingToLogcatToClient() throws RemoteException {
-        final var service = new ProtoLogService();
+        final var service = new ProtoLogConfigurationService();
 
-        final var args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+        final var args = new ProtoLogConfigurationService.RegisterClientArgs()
+                .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                        .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -236,10 +242,12 @@
 
     @Test
     public void sendDisableLoggingToLogcatToClient() throws RemoteException {
-        final ProtoLogService service = new ProtoLogService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, true));
         service.registerClient(mMockClient, args);
 
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
@@ -252,10 +260,12 @@
 
     @Test
     public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
-        final ProtoLogService service = new ProtoLogService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
@@ -267,14 +277,16 @@
 
     @Test
     public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
-        final ProtoLogService service = new ProtoLogService();
+        final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
 
         Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
         service.enableProtoLogToLogcat(TEST_GROUP);
         Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
 
-        final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
-                .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+        final ProtoLogConfigurationService.RegisterClientArgs args =
+                new ProtoLogConfigurationService.RegisterClientArgs()
+                        .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+                                .GroupConfig(TEST_GROUP, false));
         service.registerClient(mMockClient, args);
 
         Mockito.verify(mMockClient).toggleLogcat(eq(true),
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 7a4f40e..9751459 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -50,21 +50,21 @@
 
 template <typename T>
 bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
-  return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
+  return lhs->name < rhs;
 }
 
 template <typename T>
 bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
-  return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
+  return rhs->name > lhs;
 }
 
 template <typename T>
 struct NameEqualRange {
   bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
-    return less_than_struct_with_name<T>(lhs, rhs);
+    return less_than_struct_with_name(lhs, rhs);
   }
   bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
-    return greater_than_struct_with_name<T>(lhs, rhs);
+    return greater_than_struct_with_name(lhs, rhs);
   }
 };
 
@@ -74,7 +74,7 @@
   if (lhs.id != rhs.second) {
     return lhs.id < rhs.second;
   }
-  return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0;
+  return lhs.name < rhs.first;
 }
 
 template <typename T, typename Func, typename Elements>
@@ -90,14 +90,16 @@
   StringPiece product;
 };
 
-template <typename T>
-bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
-  int cmp = lhs->config.compare(*rhs.config);
-  if (cmp == 0) {
-    cmp = StringPiece(lhs->product).compare(rhs.product);
+struct lt_config_key_ref {
+  template <typename T>
+  bool operator()(const T& lhs, const ConfigKey& rhs) const noexcept {
+    int cmp = lhs->config.compare(*rhs.config);
+    if (cmp == 0) {
+      cmp = lhs->product.compare(rhs.product);
+    }
+    return cmp < 0;
   }
-  return cmp < 0;
-}
+};
 
 }  // namespace
 
@@ -159,10 +161,10 @@
 ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
                                               android::StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+                               lt_config_key_ref());
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
-    if (value->config == config && StringPiece(value->product) == product) {
+    if (value->config == config && value->product == product) {
       return value;
     }
   }
@@ -172,10 +174,10 @@
 const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
                                                     android::StringPiece product) const {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+                               lt_config_key_ref());
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
-    if (value->config == config && StringPiece(value->product) == product) {
+    if (value->config == config && value->product == product) {
       return value;
     }
   }
@@ -185,10 +187,10 @@
 ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
                                                       StringPiece product) {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+                               lt_config_key_ref());
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
-    if (value->config == config && StringPiece(value->product) == product) {
+    if (value->config == config && value->product == product) {
       return value;
     }
   }
@@ -199,36 +201,21 @@
 
 std::vector<ResourceConfigValue*> ResourceEntry::FindAllValues(const ConfigDescription& config) {
   std::vector<ResourceConfigValue*> results;
-
-  auto iter = values.begin();
+  auto iter =
+      std::lower_bound(values.begin(), values.end(), ConfigKey{&config, ""}, lt_config_key_ref());
   for (; iter != values.end(); ++iter) {
     ResourceConfigValue* value = iter->get();
-    if (value->config == config) {
-      results.push_back(value);
-      ++iter;
+    if (value->config != config) {
       break;
     }
-  }
-
-  for (; iter != values.end(); ++iter) {
-    ResourceConfigValue* value = iter->get();
-    if (value->config == config) {
-      results.push_back(value);
-    }
+    results.push_back(value);
   }
   return results;
 }
 
 bool ResourceEntry::HasDefaultValue() const {
-  const ConfigDescription& default_config = ConfigDescription::DefaultConfig();
-
   // The default config should be at the top of the list, since the list is sorted.
-  for (auto& config_value : values) {
-    if (config_value->config == default_config) {
-      return true;
-    }
-  }
-  return false;
+  return !values.empty() && values.front()->config == ConfigDescription::DefaultConfig();
 }
 
 ResourceTable::CollisionResult ResourceTable::ResolveFlagCollision(FlagStatus existing,
@@ -364,14 +351,14 @@
     if (found) {
       return &*it;
     }
-    return &*el.insert(it, std::forward<T>(value));
+    return &*el.insert(it, std::move(value));
   }
 };
 
 struct PackageViewComparer {
   bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) {
     return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>(
-        lhs, std::make_pair(rhs.name, rhs.id));
+        lhs, std::tie(rhs.name, rhs.id));
   }
 };
 
@@ -384,7 +371,7 @@
 struct EntryViewComparer {
   bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
     return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
-        lhs, std::make_pair(rhs.name, rhs.id));
+        lhs, std::tie(rhs.name, rhs.id));
   }
 };
 
@@ -429,10 +416,10 @@
 const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
                                                              android::StringPiece product) const {
   auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
-                               lt_config_key_ref<const ResourceConfigValue*>);
+                               lt_config_key_ref());
   if (iter != values.end()) {
     const ResourceConfigValue* value = *iter;
-    if (value->config == config && StringPiece(value->product) == product) {
+    if (value->config == config && value->product == product) {
       return value;
     }
   }
@@ -615,11 +602,15 @@
         result = ResolveValueCollision(config_value->value.get(), res.value.get());
       }
       switch (result) {
-        case CollisionResult::kKeepBoth:
+        case CollisionResult::kKeepBoth: {
           // Insert the value ignoring for duplicate configurations
-          entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
-          entry->values.back()->value = std::move(res.value);
+          auto it = entry->values.insert(
+              std::lower_bound(entry->values.begin(), entry->values.end(),
+                               ConfigKey{&res.config, res.product}, lt_config_key_ref()),
+              util::make_unique<ResourceConfigValue>(res.config, res.product));
+          (*it)->value = std::move(res.value);
           break;
+        }
 
         case CollisionResult::kTakeNew:
           // Take the incoming value.
diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
index 0b61948..9c44332 100644
--- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
+++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
@@ -23,13 +23,21 @@
 import java.util.zip.ZipFile
 
 fun main(args: Array<String>) {
-    if (args.size != 2) {
+    if (args.size < 2 || args.size > 3) {
         usage()
     }
 
     val zipFileName = args[0]
     val aidlFileName = args[1]
 
+    var stable = false
+    if (args.size == 3) {
+        if (args[2] != "--guarantee_stable") {
+            usage()
+        }
+        stable = true
+    }
+
     val zipFile: ZipFile
 
     try {
@@ -55,6 +63,9 @@
         val outFile = File(aidlFileName)
         val outWriter = outFile.bufferedWriter()
         for (parcelable in parcelables) {
+            if (stable) {
+                outWriter.write("@JavaOnlyStableParcelable ")
+            }
             outWriter.write("parcelable ")
             outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
             outWriter.write(";\n")